Che cos’è l’inizializzazione Double Brace in Java?

Qual è la syntax di inizializzazione Double Brace ( {{ ... }} ) in Java?

L’inizializzazione della doppia parentesi crea una class anonima derivata dalla class specificata (le parentesi esterne ) e fornisce un blocco di inizializzazione all’interno di quella class (le parentesi interne ). per esempio

 new ArrayList() {{ add(1); add(2); }}; 

Si noti che un effetto dell’utilizzo dell’inizializzazione di doppia parentesi è che si stanno creando classi interne anonime. La class creata ha implicitamente this puntatore alla class esterna circostante. Anche se normalmente non è un problema, può causare dolore in alcune circostanze, ad esempio durante la serializzazione o la raccolta dei rifiuti, e vale la pena essere consapevoli di questo.

Ogni volta che qualcuno usa l’inizializzazione doppia coppia, un gattino viene ucciso.

A parte la syntax che è piuttosto insolita e non propriamente idiomatica (il gusto è discutibile, ovviamente), si creano inutilmente due problemi significativi nella propria applicazione, che ho appena descritto più dettagliatamente qui .

1. Stai creando troppe classi anonime

Ogni volta che si utilizza l’inizializzazione doppia coppia viene creata una nuova class. 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 

Questo è un bel po ‘di overhead per il tuo classloader – per niente! Naturalmente non ci vorrà molto tempo di inizializzazione se lo fai una volta. Ma se lo fai 20’000 volte in tutta l’applicazione aziendale … tutta la memoria heap solo per un po ‘di “zucchero syntax”?

2. Stai potenzialmente creando una perdita di memoria!

Se si prende il codice di cui sopra e si restituisce la mappa da un metodo, i chiamanti di quel metodo potrebbero non tener conto delle 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; } } 

La Map restituita conterrà ora un riferimento all’istanza di ReallyHeavyObject . Probabilmente non vuoi rischiare che:

Perdita di memoria qui

Immagine da http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/

3. Puoi fingere che Java abbia letterali di mappa

Per rispondere alla tua domanda, le persone hanno utilizzato questa syntax per far finta che Java abbia qualcosa come le letterali di mappa, simile ai letterali di array esistenti:

 String[] array = { "John", "Doe" }; Map map = new HashMap() {{ put("John", "Doe"); }}; 

Alcune persone possono trovare questo sintatticamente stimolante.

  • La prima parentesi crea una nuova class interna anonima.
  • Il secondo set di parentesi crea un inizializzatore di istanza come un blocco statico in Class.

Per esempio:

  public class TestHashMap { public static void main(String[] args) { HashMap map = new HashMap(){ { put("1", "ONE"); }{ put("2", "TWO"); }{ put("3", "THREE"); } }; Set keySet = map.keySet(); for (String string : keySet) { System.out.println(string+" ->"+map.get(string)); } } } 

Come funziona

La prima parentesi crea una nuova class interiore anonima. Queste classi interne sono in grado di accedere al comportamento della loro class genitore. Quindi, nel nostro caso, stiamo creando una sottoclass di classi HashSet, quindi questa class interna è in grado di utilizzare il metodo add ().

E il secondo set di parentesi non è altro che inizializzatori di istanze. Se si ricordano i concetti di base di Java, è ansible associare facilmente i blocchi di inizializzazione di istanza con gli inizializzatori statici a causa di una struttura simile a quella di una struttura simile. L’unica differenza è che l’inizializzatore statico viene aggiunto con la parola chiave static e viene eseguito una sola volta; non importa quanti oggetti crei.

Di Più

Per una divertente applicazione di inizializzazione doppia coppia, vedi 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 il BattleOfGrottoOfSausageSmells e … un grosso bacon!

Penso che sia importante sottolineare che non esiste una cosa come l’inizializzazione di Double Brace in Java . Il sito Web Oracle non ha questo termine. In questo esempio ci sono due funzioni usate insieme: blocco anonimo di class e inizializzatore. Sembra che il vecchio blocco inizializzatore sia stato dimenticato dagli sviluppatori e causa una certa confusione in questo argomento. Citazione da documenti Oracle :

I blocchi di inizializzazione per le variabili di istanza appaiono come blocchi di inizializzazione statici, ma senza la parola chiave statica:

 { // whatever code is needed for initialization goes here } 

Vorrei sottolineare che non esiste un’inizializzazione di doppia parentesi. Esiste solo un blocco di inizializzazione tradizionale a un solo blocco tradizionale. Il secondo blocco di parentesi non ha nulla a che fare con l’inizializzazione. Le risposte dicono che quelle due parentesi inizializzano qualcosa, ma non è così.

In secondo luogo, 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 sia usato solo quando si creano classi interne anonime. Ma è usato in tutte le classi. Leggendo quelle risposte sembra un nuovo futuro speciale dedicato alle classi anonime e penso che sia fuorviante.

Andando oltre, questa domanda parla della situazione in cui la staffa di seconda apertura è appena dopo la staffa di prima apertura. Se usato normalmente nella class normale, c’è un codice tra due parentesi, ma è totalmente la stessa cosa. Quindi si tratta di posizionare le parentesi. Quindi penso che non dovremmo dire che questa è una nuova cosa eccitante, perché questa è la cosa che tutti conosciamo, ma appena scritta con un po ‘di codice tra parentesi. Non dovremmo creare un nuovo concetto chiamato “doppia parentesi di inizializzazione”.

Non sono d’accordo con un argomento che crei troppe classi anonime. Non li stai creando perché è un blocco di inizializzazione, ma solo perché li crei. Verrebbero creati anche se non si utilizzavano due inizializzazioni di parentesi graffe in modo che tali problemi si verifichino anche senza inizializzazione … L’inizializzazione non è il fattore che crea l’object inizializzato.

Inoltre, non dovremmo parlare del problema creato usando questa “non valida” inizializzazione della doppia parentesi o anche con la normale inizializzazione di una parentesi, perché i problemi descritti esistono solo a causa della creazione di una class anonima, quindi non ha nulla a che fare con la domanda originale. Ma tutte le risposte danno ai lettori un’impressione che non è colpa della creazione di classi anonime, ma di questa cosa malvagia (inesistente) chiamata “doppia parentesi di inizializzazione”.

Per evitare tutti gli effetti negativi dell’inizializzazione con doppia parentesi, come ad esempio:

  1. Rotta “uguale” compatibilità.
  2. Nessun controllo eseguito, quando si utilizzano assegnazioni dirette.
  3. Possibili perdite di memoria.

fare le prossime cose:

  1. Crea una class “Builder” specifica per l’inizializzazione della doppia parentesi.
  2. Dichiara i campi con valori predefiniti.
  3. Metti il ​​metodo di creazione dell’object in quella class.

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:

  1. Semplicemente da usare.
  2. Non rompere la compatibilità “uguale”.
  3. È ansible eseguire controlli nel metodo di creazione.
  4. Nessuna perdita di memoria.

svantaggi:

  • Nessuna.

E, di conseguenza, abbiamo il più semplice schema di java builder di sempre.

Vedi tutti gli esempi su github: java-sf-builder-simple-example

È – tra gli altri usi – una scorciatoia per inizializzare le raccolte. Per saperne di più …

Intendi qualcosa di simile?

 List blah = new ArrayList(){{add("asdfa");add("bbb");}}; 

è un inizializzazione della lista di array in tempo di creazione (hack)

Puoi inserire alcune istruzioni Java come loop per inizializzare la raccolta:

 List characters = new ArrayList() { { for (char c = 'A'; c <= 'E'; c++) add(c); } }; 

 Random rnd = new Random(); List integers = new ArrayList() { { while (size() < 10) add(rnd.nextInt(1_000_000)); } }; 

Ma questo caso influisce sulle prestazioni, controlla questa discussione

Questo sembrerebbe essere lo stesso della parola chiave with così popolare in flash e vbscript. È un metodo per cambiare ciò che è e niente di più.