Was ist die Double Brace-Initialisierung in Java?

307

Was ist die Double Brace-Initialisierungssyntax ( {{ ... }}) in Java?

Sgokhales
quelle
2
Siehe auch stackoverflow.com/q/924285/45935
Jim Ferrans
10
Die Doppelklammerinitialisierung ist eine sehr gefährliche Funktion und sollte mit Bedacht verwendet werden. Es kann gleich Vertrag brechen und knifflige Speicherlecks verursachen. Dieser Artikel beschreibt die Details.
Andrii Polunin

Antworten:

303

Die Initialisierung mit doppelten Klammern erstellt eine anonyme Klasse, die von der angegebenen Klasse (den äußeren Klammern) abgeleitet ist, und stellt einen Initialisierungsblock innerhalb dieser Klasse (die inneren Klammern) bereit . z.B

new ArrayList<Integer>() {{
   add(1);
   add(2);
}};

Beachten Sie, dass die Verwendung dieser doppelten Klammerinitialisierung dazu führt, dass Sie anonyme innere Klassen erstellen. Die erstellte Klasse hat einen impliziten thisZeiger auf die umgebende äußere Klasse. Obwohl dies normalerweise kein Problem ist, kann es unter bestimmten Umständen zu Trauer führen, z. B. beim Serialisieren oder Sammeln von Müll, und es lohnt sich, sich dessen bewusst zu sein.

Brian Agnew
quelle
11
Vielen Dank, dass Sie die Bedeutung der inneren und äußeren Klammern geklärt haben. Ich habe mich gefragt, warum plötzlich zwei Klammern mit einer besonderen Bedeutung erlaubt sind, obwohl es sich tatsächlich um normale Java-Konstrukte handelt, die nur als magischer neuer Trick erscheinen. Solche Dinge lassen mich jedoch die Java-Syntax in Frage stellen. Wenn Sie noch kein Experte sind, kann das Lesen und Schreiben sehr schwierig sein.
Jackthehipster
4
Eine solche "magische Syntax" gibt es in vielen Sprachen, zum Beispiel unterstützen fast alle C-ähnlichen Sprachen die Syntax "geht zu 0" von "x -> 0" in for-Schleifen, die nur "x -> 0" mit seltsam ist Platzplatzierung.
Joachim Sauer
17
Wir können nur schlussfolgern, dass die "doppelte Klammerinitialisierung" nicht für sich allein existiert, sondern nur eine Kombination aus der Erstellung einer anonymen Klasse und eines Initialisierungsblocks , der nach seiner Kombination wie ein syntaktisches Konstrukt aussieht , in Wirklichkeit jedoch nicht. t.
MC Emperor
Danke dir! Gson gibt null zurück, wenn wir etwas mit doppelter Klammerinitialisierung aufgrund der anonymen Verwendung der inneren Klasse serialisieren.
Pradeep AJ-Msft MVP
295

Jedes Mal, wenn jemand eine Doppelklammerinitialisierung verwendet, wird ein Kätzchen getötet.

Abgesehen davon, dass die Syntax eher ungewöhnlich und nicht wirklich idiomatisch ist (Geschmack ist natürlich umstritten), verursachen Sie unnötigerweise zwei signifikante Probleme in Ihrer Anwendung, über die ich kürzlich hier ausführlicher gebloggt habe .

1. Sie erstellen viel zu viele anonyme Klassen

Jedes Mal, wenn Sie die doppelte Klammerinitialisierung verwenden, wird eine neue Klasse erstellt. ZB dieses Beispiel:

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");
        }});
    }});
}};

... wird diese Klassen produzieren:

Test$1$1$1.class
Test$1$1$2.class
Test$1$1.class
Test$1.class
Test.class

Das ist ziemlich viel Aufwand für Ihren Klassenlader - für nichts! Natürlich dauert die Initialisierung nicht lange, wenn Sie dies einmal tun. Aber wenn Sie dies 20'000 Mal in Ihrer Unternehmensanwendung tun ... all diesen Heap-Speicher nur für ein bisschen "Syntaxzucker"?

2. Sie verursachen möglicherweise einen Speicherverlust!

Wenn Sie den obigen Code verwenden und diese Zuordnung von einer Methode zurückgeben, halten Aufrufer dieser Methode möglicherweise ahnungslos an sehr schweren Ressourcen fest, die nicht durch Müll gesammelt werden können. Betrachten Sie das folgende Beispiel:

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;
    }
}

Die zurückgegebene MapDatei enthält jetzt einen Verweis auf die einschließende Instanz von ReallyHeavyObject. Sie wollen das wahrscheinlich nicht riskieren:

Speicherleck genau hier

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

3. Sie können so tun, als hätte Java Kartenliterale

Um Ihre eigentliche Frage zu beantworten, haben die Benutzer diese Syntax verwendet, um vorzutäuschen, dass Java so etwas wie Kartenliterale hat, ähnlich den vorhandenen Array-Literalen:

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

Einige Leute mögen dies syntaktisch anregend finden.

Lukas Eder
quelle
11
"Sie erstellen viel zu viele anonyme Klassen" - wenn ich mir anschaue, wie (sagen wir) Scala anonyme Klassen erstellt, bin ich mir nicht sicher, ob dies ein großes Problem ist
Brian Agnew
2
Bleibt es nicht eine gültige und nette Möglichkeit, statische Karten zu deklarieren? Wenn eine HashMap mit {{...}}einem staticFeld initialisiert und als Feld deklariert wird, sollte es keinen möglichen Speicherverlust geben, nur eine anonyme Klasse und keine eingeschlossenen Instanzreferenzen, oder?
Lorenzo-s
8
@ lorenzo-s: Ja, 2) und 3) gelten dann nicht, nur 1). Glücklicherweise gibt es mit Java 9 endlich Map.of()diesen Zweck, so dass dies eine bessere Lösung sein wird
Lukas Eder
3
Es könnte erwähnenswert sein, dass die inneren Karten auch Verweise auf die äußeren Karten und damit indirekt auf enthalten ReallyHeavyObject. Außerdem erfassen anonyme innere Klassen alle lokalen Variablen, die im Klassenkörper verwendet werden. Wenn Sie also nicht nur Konstanten verwenden, um Sammlungen oder Zuordnungen mit diesem Muster zu initialisieren, erfassen die Instanzen der inneren Klasse alle und referenzieren sie auch dann, wenn sie tatsächlich aus entfernt werden die Sammlung oder Karte. In diesem Fall benötigen diese Instanzen nicht nur doppelt so viel Speicher für die Referenzen, sondern weisen diesbezüglich einen weiteren Speicherverlust auf.
Holger
41
  • Die erste Klammer erstellt eine neue anonyme innere Klasse.
  • Der zweite Satz von Klammern erstellt Instanzinitialisierer wie den statischen Block in Class.

Zum Beispiel:

   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));
        }
    }

}

Wie es funktioniert

Die erste Klammer erstellt eine neue anonyme innere Klasse. Diese inneren Klassen können auf das Verhalten ihrer übergeordneten Klasse zugreifen. In unserem Fall erstellen wir also tatsächlich eine Unterklasse der HashSet-Klasse, sodass diese innere Klasse die put () -Methode verwenden kann.

Und der zweite Satz von geschweiften Klammern ist nichts anderes als Instanzinitialisierer. Wenn Sie an Java-Kernkonzepte erinnern, können Sie statische Initialisiererblöcke aufgrund ähnlicher Klammern wie struct problemlos mit statischen Initialisierern verknüpfen. Der einzige Unterschied besteht darin, dass der statische Initialisierer mit dem Schlüsselwort static hinzugefügt und nur einmal ausgeführt wird. egal wie viele Objekte Sie erstellen.

Mehr

Premraj
quelle
24

Eine unterhaltsame Anwendung der Doppelklammerinitialisierung finden Sie hier Dwemthys Array in Java .

Ein Ausschnitt

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;
  }}

Und jetzt sei bereit für den BattleOfGrottoOfSausageSmellsund… klobigen Speck!

akuhn
quelle
16

Ich denke, es ist wichtig zu betonen, dass es in Java keine "Double Brace-Initialisierung" gibt . Die Oracle-Website hat diesen Begriff nicht. In diesem Beispiel werden zwei Funktionen zusammen verwendet: anonyme Klasse und Initialisierungsblock. Scheint, als ob der alte Initialisierungsblock von den Entwicklern vergessen wurde und in diesem Thema einige Verwirrung stiftet. Zitat aus Oracle-Dokumenten :

Initialisierungsblöcke für Beispielvariablen sehen genauso aus wie statische Initialisierungsblöcke, jedoch ohne das statische Schlüsselwort:

{
    // whatever code is needed for initialization goes here
}
Alex T.
quelle
11

1- Es gibt keine doppelten Klammern:
Ich möchte darauf hinweisen, dass es keine doppelte Klammerinitialisierung gibt. Es gibt nur einen normalen traditionellen Initialisierungsblock für eine Zahnspange. Der zweite Klammerblock hat nichts mit der Initialisierung zu tun. Antworten sagen, dass diese beiden Klammern etwas initialisieren, aber es ist nicht so.

2- Es geht nicht nur um anonyme Klassen, sondern um alle Klassen:
Fast alle Antworten sprechen davon, dass dies beim Erstellen anonymer innerer Klassen verwendet wird. Ich denke, dass Leute, die diese Antworten lesen, den Eindruck haben, dass dies nur beim Erstellen anonymer innerer Klassen verwendet wird. Aber es wird in allen Klassen verwendet. Das Lesen dieser Antworten scheint eine brandneue Besonderheit zu sein, die anonymen Klassen gewidmet ist, und ich denke, das ist irreführend.

3- Der Zweck besteht nur darin, Klammern nacheinander zu platzieren, nicht um ein neues Konzept:
In dieser Frage geht es um die Situation, in der sich die zweite öffnende Klammer unmittelbar nach der ersten öffnenden Klammer befindet. In der normalen Klasse gibt es normalerweise Code zwischen zwei geschweiften Klammern, aber es ist völlig dasselbe. Es geht also darum, Klammern zu setzen. Ich denke, wir sollten nicht sagen, dass dies eine neue aufregende Sache ist, denn dies ist die Sache, die wir alle kennen, sondern nur mit einem Code in Klammern geschrieben. Wir sollten kein neues Konzept namens "Double Brace Initialization" erstellen.

4- Das Erstellen verschachtelter anonymer Klassen hat nichts mit zwei geschweiften Klammern zu tun:
Ich stimme einem Argument nicht zu, dass Sie zu viele anonyme Klassen erstellen. Sie erstellen sie nicht als Initialisierungsblock, sondern nur, weil Sie sie erstellen. Sie würden auch dann erstellt, wenn Sie keine Initialisierung mit zwei geschweiften Klammern verwenden würden, sodass diese Probleme auch ohne Initialisierung auftreten würden ... Die Initialisierung ist nicht der Faktor, der initialisierte Objekte erstellt.

Außerdem sollten wir nicht über Probleme sprechen, die durch die Verwendung dieser nicht existierenden "Doppelklammer-Initialisierung" oder sogar durch die normale Ein-Klammer-Initialisierung verursacht wurden, da beschriebene Probleme nur aufgrund der Erstellung einer anonymen Klasse bestehen und daher nichts mit der ursprünglichen Frage zu tun haben. Aber alle Antworten mit geben dem Leser den Eindruck, dass es nicht die Schuld ist, anonyme Klassen zu erstellen, sondern dieses böse (nicht existierende) Ding, das als "Doppelklammer-Initialisierung" bezeichnet wird.

ctomek
quelle
9

Um alle negativen Auswirkungen der Doppelklammerinitialisierung zu vermeiden, wie z.

  1. Defekte Kompatibilität "gleich".
  2. Bei direkten Zuweisungen werden keine Überprüfungen durchgeführt.
  3. Mögliche Speicherlecks.

mache die nächsten Dinge:

  1. Erstellen Sie eine separate "Builder" -Klasse, insbesondere für die Initialisierung von Doppelklammern.
  2. Deklarieren Sie Felder mit Standardwerten.
  3. Fügen Sie die Objekterstellungsmethode in diese Klasse ein.

Beispiel:

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 ; }
}

Verwendungszweck:

MyClass my = new MyClass.Builder(){{ first = 1; third = "3"; }}.create();

Vorteile:

  1. Einfach zu bedienen.
  2. Unterbricht nicht die Kompatibilität "gleich".
  3. Sie können Überprüfungen in der Erstellungsmethode durchführen.
  4. Keine Speicherlecks.

Nachteile:

  • Keiner.

Infolgedessen haben wir das einfachste Java Builder-Muster aller Zeiten.

Alle Beispiele finden Sie unter github: java-sf-builder-simple-example

Boris Podchezertsev
quelle
4

Es ist unter anderem eine Verknüpfung zum Initialisieren von Sammlungen. Mehr erfahren ...

miku
quelle
2
Nun, das ist eine Anwendung dafür, aber keineswegs die einzige.
Skaffman
4

du meinst so etwas?

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

Es ist eine Array-Listen-Initialisierung in der Erstellungszeit (Hack)

Dhblah
quelle
4

Sie können einige Java-Anweisungen als Schleife einfügen, um die Sammlung zu initialisieren:

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));
    }
};

Dieser Fall wirkt sich jedoch auf die Leistung aus. Überprüfen Sie diese Diskussion

Anton Dozortsev
quelle
4

Wie von @Lukas Eder hervorgehoben, muss die Initialisierung von Sammlungen mit Doppelklammern vermieden werden.

Es wird eine anonyme innere Klasse erstellt, und da alle internen Klassen einen Verweis auf die übergeordnete Instanz behalten, kann - und wird dies zu 99% wahrscheinlich - die Garbage Collection verhindern, wenn auf diese Collection-Objekte mehr Objekte als nur das deklarierende referenziert werden.

Java 9 hat bequeme Methoden eingeführt List.of, Set.ofund Map.of, die stattdessen verwendet werden soll. Sie sind schneller und effizienter als der Double-Brace-Initialisierer.

Mikhail Kholodkov
quelle
0

Die erste Klammer erstellt eine neue anonyme Klasse und die zweite Klammer erstellt Instanzinitialisierer wie den statischen Block.

Wie andere darauf hingewiesen haben, ist die Verwendung nicht sicher.

Sie können diese Alternative jedoch immer zum Initialisieren von Sammlungen verwenden.

  • Java 8
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
  • Java 9
List<String> list = List.of("A", "B", "C");
Ankit Sharma
quelle
-1

Dies scheint dasselbe zu sein wie das in Flash und VBScript so beliebte Schlüsselwort with. Es ist eine Methode, um zu ändern, was thisist und nichts weiter.

Chuck Vose
quelle
Nicht wirklich. Das wäre so, als würde man sagen, dass das Erstellen einer neuen Klasse eine Methode ist, um das zu ändern, was thisist. Die Syntax erstellt lediglich eine anonyme Klasse (daher thisbezieht sich jeder Verweis auf das Objekt dieser neuen anonymen Klasse) und verwendet dann einen Initialisierungsblock {...}, um die neu erstellte Instanz zu initialisieren.
grinch