Questo è un post tradotto da IA.
[Effictive Java] Item 6. Evitare la creazione di oggetti non necessari
- Lingua di scrittura: Coreana
- •
- Paese di riferimento: Tutti i paesi
- •
- Tecnologia dell'informazione
Seleziona la lingua
Testo riassunto dall'intelligenza artificiale durumis
- Creare istanze di stringhe o Boolean usando la parola chiave new è uno spreco di memoria, quindi è meglio dichiararle come letterali o usare il metodo Boolean.valueOf().
- Il metodo String.matches() usa le espressioni regolari, quindi può causare problemi di prestazioni. È meglio mettere in cache l'istanza di Pattern e riutilizzarla.
- Quando si restituisce un oggetto di visualizzazione, come il metodo keySet(), è più sicuro usare la copia difensiva per restituire un nuovo oggetto.
Quando si creano oggetti superflui
Utilizzo di new String()
String a = new String("hi");
String b = new String("hi");
Le stringhe a, b e c avranno tutte la stringa "hi". Tuttavia, poiché gli indirizzi a cui questi tre stringhe fanno riferimento sono tutti diversi, si verifica uno spreco in termini di allocazione di memoria separata per gli stessi dati.
Pertanto, quando si dichiarano le stringhe, è necessario utilizzare i letterali invece della parola chiave new.
String a = "hi";
String b = "hi";
Il codice sorgente precedente utilizza una sola istanza. Inoltre, l'utilizzo di questo metodo garantisce che tutti i codici che utilizzano il letterale di stringa "hi" nello stesso JVM riutilizzino lo stesso oggetto. Questo è dovuto alla caratteristica del pool di costanti Java.
Utilizzo di new Boolean()
Il codice precedente sta creando un'istanza Boolean utilizzando il costruttore che accetta una stringa come parametro. Boolean può essere solo true o false, quindi la creazione di un'istanza ogni volta è uno spreco di memoria. Pertanto, è meglio utilizzare il metodo di fabbrica statico Boolean.valueOf().
Utilizzo di String.matches()
È meglio memorizzare nella cache e riutilizzare gli oggetti costosi da creare, ma non sempre sappiamo il costo degli oggetti che creiamo. Ad esempio, se volessimo scrivere un metodo per verificare se una stringa data è un numero romano valido, il modo più semplice sarebbe quello di utilizzare un'espressione regolare come la seguente.
public static boolean isRomanNumeral(String s) {
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
Tuttavia, String.matches() è un metodo con problemi di prestazioni. L'istanza Pattern utilizzata internamente da questo metodo per l'espressione regolare viene creata una sola volta e quindi eliminata, diventando immediatamente un obiettivo per la garbage collection. Se la frequenza di utilizzo di questa espressione regolare aumenta, il costo di creazione ed eliminazione della stessa istanza Pattern aumenta. Pertanto, è meglio memorizzare nella cache l'istanza Pattern in anticipo e riutilizzarla ogni volta che il metodo isRomanNumeral() viene chiamato.
public class RomanNumerals {
private static final Pattern ROMAN = Pattern.compile(
"^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
public static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
Attenzione
In tutti gli esempi precedenti, abbiamo reso immutabili tutti gli oggetti memorizzati nella cache per evitare la creazione di oggetti inutili. Questo perché è sicuro riutilizzarli. Tuttavia, ci sono casi in cui questo è in contrasto con l'intuizione di riutilizzare gli oggetti immutabili.
Gli adattatori (viste) sono oggetti che delegano il lavoro effettivo agli oggetti sottostanti e fungono da seconda interfaccia. Gli adattatori devono solo gestire gli oggetti sottostanti, quindi è sufficiente creare un solo adattatore per ogni oggetto sottostante.
Ad esempio, il metodo keySet() dell'interfaccia Map restituisce una vista Set di tutte le chiavi all'interno dell'oggetto Map. Si potrebbe pensare che una nuova istanza Set venga creata ogni volta che si chiama il metodo keySet(), ma se si guarda effettivamente l'implementazione del JDK, viene restituita la stessa istanza Set variabile ogni volta.
Questo perché tutte le istanze Set svolgono la stessa funzione, anche se l'istanza Set restituita è variabile, e tutte le istanze Set rappresentano l'istanza Map. Pertanto, non importa se keySet() crea più oggetti vista, ma non c'è bisogno né beneficio.
public class UsingKeySet {
public static void main(String[] args) {
Map menu = new HashMap<>();
menu.put("Burger", 8);
menu.put("Pizza", 9);
Set names1 = menu.keySet();
Set names2 = menu.keySet();
names1.remove("Burger");
System.out.println(names1.size()); // 1
System.out.println(names2.size()); // 1
}
Pertanto, se si modifica l'istanza names1 come sopra, anche l'istanza names2 ne risentirà.
Tuttavia, personalmente credo che il valore restituito dal metodo keySet() dovrebbe utilizzare la copia difensiva per restituire un nuovo oggetto ogni volta. Se l'istanza Set ricevuta dal metodo keySet() viene utilizzata anche in altri punti e c'è del codice che cambia lo stato di questa istanza, non saremmo più certi dello stato corrente dell'istanza Set e dell'istanza Map.
Inoltre, a meno che l'ambiente non utilizzi eccessivamente keySet(), il fatto che l'interfaccia Set venga creata ogni volta non influisce in modo sostanziale sulle prestazioni. Penso che sia meglio rendere l'interfaccia Set immutabile per mantenere la stabilità della manutenzione.
Autoboxing
L'autoboxing è una tecnica che converte automaticamente i tipi primitivi e i tipi wrapper tra loro quando un programmatore li mescola. Tuttavia, l'autoboxing non elimina completamente la distinzione tra tipi primitivi e wrapper.
public static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
return sum;
Logicamente non ci sono problemi, ma il codice è molto inefficiente in termini di prestazioni. Questo è dovuto al tipo di sum e al tipo di i all'interno del ciclo for.
Il tipo di sum è Long, mentre il tipo di i è long. Ciò significa che il tipo long di i creerà una nuova istanza Long ogni volta che verrà aggiunto a sum durante il ciclo. Di conseguenza, è necessario utilizzare i tipi primitivi invece dei tipi wrapper e fare attenzione a evitare l'autoboxing non intenzionale.
Cosa non va interpretato male
Non si deve pensare che evitare la creazione di oggetti inutili significhi semplicemente che il costo della creazione di oggetti è elevato e quindi va evitato.
Soprattutto nella JVM di oggi, la creazione ed eliminazione di piccoli oggetti inutili non è un compito così impegnativo. Pertanto, a meno che non si tratti di oggetti molto costosi come le connessioni al database, non creare un pool di oggetti personalizzato.
Inoltre, ricordare che i danni causati dal riutilizzo di oggetti in situazioni in cui è necessaria una copia difensiva sono molto più grandi dei danni causati dalla creazione ripetuta di oggetti inutili. Gli effetti collaterali della creazione ripetuta influiscono solo sulla forma del codice e sulle prestazioni, mentre il fallimento della copia difensiva porta direttamente a bug e problemi di sicurezza.