Che cos'è la sintassi di inizializzazione di Double Brace ( {{ ... }}
) in Java?
Che cos'è la sintassi di inizializzazione di Double Brace ( {{ ... }}
) in Java?
Risposte:
L'inizializzazione della doppia parentesi graffa crea una classe anonima derivata dalla classe specificata (le parentesi graffe esterne ) e fornisce un blocco inizializzatore all'interno di quella classe ( parentesi graffe interne ). per esempio
new ArrayList<Integer>() {{
add(1);
add(2);
}};
Si noti che un effetto dell'utilizzo di questa inizializzazione a doppia parentesi è che si stanno creando classi interne anonime. La classe creata ha un this
puntatore implicito alla classe esterna circostante. Sebbene normalmente non sia un problema, può causare dolore in alcune circostanze, ad esempio durante la serializzazione o la raccolta dei rifiuti, e vale la pena esserne consapevoli.
Ogni volta che qualcuno usa l'inizializzazione con doppio tutore, un gattino viene ucciso.
A parte la sintassi che è piuttosto insolita e non proprio idiomatica (il gusto è discutibile, ovviamente), stai creando inutilmente due problemi significativi nella tua applicazione, di cui ho recentemente scritto un blog più dettagliatamente qui .
Ogni volta che si utilizza l'inizializzazione con doppio controvento, viene creata una nuova classe. Ad esempio questo esempio:
Map source = new HashMap(){{
put("firstName", "John");
put("lastName", "Smith");
put("organizations", new HashMap(){{
put("0", new HashMap(){{
put("id", "1234");
}});
put("abc", new HashMap(){{
put("id", "5678");
}});
}});
}};
... produrrà queste classi:
Test$1$1$1.class
Test$1$1$2.class
Test$1$1.class
Test$1.class
Test.class
È un bel po 'di sovraccarico per il tuo classloader - per niente! Ovviamente non ci vorrà molto tempo di inizializzazione se lo fai una volta. Ma se lo fai 20'000 volte in tutta la tua applicazione aziendale ... tutta quella memoria dell'heap solo per un po 'di "zucchero della sintassi"?
Se si prende il codice sopra riportato e si restituisce quella mappa da un metodo, i chiamanti di quel metodo potrebbero trattenere inconsapevolmente risorse molto pesanti che non possono essere raccolte. Considera il seguente esempio:
public class ReallyHeavyObject {
// Just to illustrate...
private int[] tonsOfValues;
private Resource[] tonsOfResources;
// This method almost does nothing
public Map quickHarmlessMethod() {
Map source = new HashMap(){{
put("firstName", "John");
put("lastName", "Smith");
put("organizations", new HashMap(){{
put("0", new HashMap(){{
put("id", "1234");
}});
put("abc", new HashMap(){{
put("id", "5678");
}});
}});
}};
return source;
}
}
Il reso Map
ora conterrà un riferimento all'istanza allegata di ReallyHeavyObject
. Probabilmente non vuoi rischiare che:
Immagine da http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/
Per rispondere alla tua vera domanda, le persone hanno utilizzato questa sintassi per fingere che Java abbia qualcosa di simile ai letterali delle mappe, simili ai letterali dell'array esistenti:
String[] array = { "John", "Doe" };
Map map = new HashMap() {{ put("John", "Doe"); }};
Alcune persone possono trovare questo sintatticamente stimolante.
{{...}}
e dichiarata come static
campo, non dovrebbe esserci alcuna perdita di memoria possibile, solo una classe anonima e nessun riferimento di istanza incluso, giusto?
Map.of()
quello scopo, quindi sarà una soluzione migliore
ReallyHeavyObject
. Inoltre, le classi interne anonime catturano tutte le variabili locali utilizzate all'interno del corpo della classe, quindi se si utilizzano non solo costanti per inizializzare raccolte o mappe con questo modello, le istanze della classe interna acquisiranno tutte e continueranno a fare riferimento a esse anche se effettivamente rimosse da la raccolta o la mappa. In tal caso, queste istanze non richiedono solo il doppio della memoria necessaria per i riferimenti, ma presentano un'altra perdita di memoria a tale riguardo.
Per esempio:
public class TestHashMap {
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<String,String>(){
{
put("1", "ONE");
}{
put("2", "TWO");
}{
put("3", "THREE");
}
};
Set<String> keySet = map.keySet();
for (String string : keySet) {
System.out.println(string+" ->"+map.get(string));
}
}
}
Come funziona
Primo tutore crea una nuova classe interna anonima. Queste classi interne sono in grado di accedere al comportamento della loro classe genitore. Quindi, nel nostro caso, stiamo effettivamente creando una sottoclasse della classe HashSet, quindi questa classe interna è in grado di usare il metodo put ().
E il secondo set di parentesi graffe non è altro che inizializzatori di istanza. Se ricordi i concetti java di base, puoi facilmente associare blocchi di inizializzatori di istanze a inizializzatori statici a causa di una struttura simile a parentesi graffe. L'unica differenza è che l'inizializzatore statico viene aggiunto con una parola chiave statica e viene eseguito una sola volta; non importa quanti oggetti crei.
Per un'applicazione divertente di inizializzazione con doppio controvento, vedere qui Dwemthy's Array in Java .
Un estratto
private static class IndustrialRaverMonkey
extends Creature.Base {{
life = 46;
strength = 35;
charisma = 91;
weapon = 2;
}}
private static class DwarvenAngel
extends Creature.Base {{
life = 540;
strength = 6;
charisma = 144;
weapon = 50;
}}
E ora, preparati per la pancettaBattleOfGrottoOfSausageSmells
e ... grosso!
Penso che sia importante sottolineare che non esiste qualcosa come "Inizializzazione Double Brace" in Java . Il sito Oracle non ha questo termine. In questo esempio ci sono due funzioni usate insieme: classe anonima e blocco di inizializzatore. Sembra che il vecchio blocco di inizializzazione sia stato dimenticato dagli sviluppatori e causi confusione in questo argomento. Citazione dai documenti Oracle :
I blocchi di inizializzatori per le variabili di istanza sembrano blocchi di inizializzatori statici, ma senza la parola chiave static:
{
// whatever code is needed for initialization goes here
}
1- Non esiste una parentesi graffa doppia:
vorrei sottolineare che non esiste una inizializzazione di parentesi graffe doppie. Esiste solo un normale blocco di inizializzazione di una parentesi graffa tradizionale. Il secondo blocco di parentesi graffe non ha nulla a che fare con l'inizializzazione. Le risposte dicono che quelle due parentesi graffe inizializzano qualcosa, ma non è così.
2- Non si tratta solo di classi anonime ma di tutte le classi:
quasi tutte le risposte dicono che è una cosa usata quando si creano classi interne anonime. Penso che le persone che leggono quelle risposte avranno l'impressione che questo venga usato solo quando si creano classi interne anonime. Ma è usato in tutte le classi. Leggere queste risposte sembra che sia una nuovissima funzione speciale dedicata alle lezioni anonime e penso che sia fuorviante.
3- Lo scopo è solo quello di posizionare le parentesi una dopo l'altra, non un nuovo concetto:
andando oltre, questa domanda parla della situazione in cui la seconda parentesi di apertura è subito dopo la prima parentesi di apertura. Se usato in una classe normale di solito c'è del codice tra due parentesi graffe, ma è totalmente la stessa cosa. Quindi si tratta di posizionare parentesi. Quindi penso che non dovremmo dire che questa è una nuova cosa eccitante, perché questa è la cosa che tutti sappiamo, ma appena scritta con un po 'di codice tra parentesi. Non dovremmo creare un nuovo concetto chiamato "inizializzazione della doppia parentesi graffa".
4- La creazione di classi anonime nidificate non ha nulla a che fare con due parentesi graffe:
non condivido l'argomento secondo cui si creano troppe classi anonime. Non li stai creando perché un blocco di inizializzazione, ma solo perché li crei. Saranno creati anche se non si utilizzassero due inizializzazioni di parentesi graffe, quindi tali problemi si verificherebbero anche senza inizializzazione ... L'inizializzazione non è il fattore che crea oggetti inizializzati.
Inoltre, non dovremmo parlare del problema creato utilizzando questa cosa inesistente "inizializzazione a doppia parentesi" o anche dalla normale inizializzazione a una parentesi, perché i problemi descritti esistono solo a causa della creazione di una classe anonima, quindi non ha nulla a che fare con la domanda originale. Ma tutte le risposte danno ai lettori l'impressione che non sia colpa della creazione di classi anonime, ma questa cosa malvagia (inesistente) chiamata "inizializzazione a doppia coppia".
Per evitare tutti gli effetti negativi dell'inizializzazione del doppio controvento, come ad esempio:
fai le prossime cose:
Esempio:
public class MyClass {
public static class Builder {
public int first = -1 ;
public double second = Double.NaN;
public String third = null ;
public MyClass create() {
return new MyClass(first, second, third);
}
}
protected final int first ;
protected final double second;
protected final String third ;
protected MyClass(
int first ,
double second,
String third
) {
this.first = first ;
this.second= second;
this.third = third ;
}
public int first () { return first ; }
public double second() { return second; }
public String third () { return third ; }
}
Uso:
MyClass my = new MyClass.Builder(){{ first = 1; third = "3"; }}.create();
vantaggi:
svantaggi:
E, di conseguenza, abbiamo il modello java builder più semplice di sempre.
Guarda tutti gli esempi su github: java-sf-builder-simple-example
È - tra gli altri usi - una scorciatoia per inizializzare le raccolte. Per saperne di più ...
È possibile inserire alcune istruzioni Java come loop per inizializzare la raccolta:
List<Character> characters = new ArrayList<Character>() {
{
for (char c = 'A'; c <= 'E'; c++) add(c);
}
};
Random rnd = new Random();
List<Integer> integers = new ArrayList<Integer>() {
{
while (size() < 10) add(rnd.nextInt(1_000_000));
}
};
Come sottolineato da @Lukas, Eder deve evitare l'inizializzazione delle doppie parentesi graffe.
Crea una classe interna anonima e poiché tutte le classi interne mantengono un riferimento all'istanza padre può - e probabilmente il 99% lo eviterà - impedire la garbage collection se questi oggetti della raccolta sono referenziati da più oggetti oltre a quello dichiarante.
Java 9 ha introdotto metodi di convenienza List.of
, Set.of
e Map.of
, che dovrebbero essere utilizzati al posto. Sono più veloci ed efficienti dell'inizializzatore a doppia parentesi.
La prima parentesi graffa crea una nuova classe anonima e la seconda serie di controventi crea un inizializzatore di istanza come il blocco statico.
Come altri hanno sottolineato, non è sicuro da usare.
Tuttavia, puoi sempre utilizzare questa alternativa per inizializzare le raccolte.
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
List<String> list = List.of("A", "B", "C");
Questo sembrerebbe essere lo stesso della parola chiave with così popolare in flash e vbscript. È un metodo per cambiare ciò che this
è e niente di più.
this
è. La sintassi crea solo una classe anonima (quindi qualsiasi riferimento a this
farebbe riferimento all'oggetto di quella nuova classe anonima), quindi utilizza un blocco {...}
di inizializzatore per inizializzare l'istanza appena creata.