it-swarm.com.de

Effizienz von Java "Double Brace Initialization"?

In Hidden Features of Java wird in der oberen Antwort Double Brace Initialization mit einer very verlockenden Syntax erwähnt:

Set<String> flavors = new HashSet<String>() {{
    add("Vanilla");
    add("strawberry");
    add("chocolate");
    add("butter pecan");
}};

Dieses Idiom erstellt eine anonyme innere Klasse mit nur einem Instanzinitialisierer, der "beliebige Methoden im [...] enthaltenen Gültigkeitsbereich verwenden kann".

Hauptfrage: Ist dies als ineffizient , wie es sich anhört? Sollte seine Verwendung auf einmalige Initialisierungen beschränkt sein? (Und natürlich vorführen!)

Zweite Frage: Das neue HashSet muss das "this" sein, das im Instanzinitialisierer verwendet wird ... kann jemand Licht in den Mechanismus bringen?

Dritte Frage: Ist dieses Idiom auch unklar , um es im Produktionscode zu verwenden?

Zusammenfassung: Sehr, sehr nette Antworten, danke an alle. Bei Frage (3) war man der Meinung, dass die Syntax klar sein sollte (obwohl ich gelegentlich einen Kommentar empfehlen würde, insbesondere wenn Ihr Code an Entwickler weitergegeben wird, die möglicherweise nicht mit dem Code vertraut sind).

Bei Frage (1) sollte der generierte Code schnell ausgeführt werden. Die zusätzlichen .class-Dateien verursachen ein Durcheinander von JAR-Dateien und verlangsamen den Programmstart geringfügig (danke an @coobird für die Messung). @Thilo wies darauf hin, dass die Speicherbereinigung beeinträchtigt werden kann und die Speicherkosten für die zusätzlich geladenen Klassen in einigen Fällen eine Rolle spielen können.

Frage (2) hat sich für mich als am interessantesten herausgestellt. Wenn ich die Antworten verstehe, passiert in DBI, dass die anonyme innere Klasse die Klasse des Objekts erweitert, das vom neuen Operator erstellt wird, und daher einen "this" -Wert hat, der auf die zu erstellende Instanz verweist. Sehr gepflegt.

Insgesamt empfinde ich DBI als eine Art intellektuelle Neugier. Coobird und andere weisen darauf hin, dass Sie denselben Effekt mit Arrays.asList, varargs-Methoden, Google Collections und den vorgeschlagenen Java 7 Collection-Literalen erzielen können. Neuere JVM-Sprachen wie Scala, JRuby und Groovy ebenfalls Da DBI den Klassenpfad überfüllt, das Laden von Klassen etwas verlangsamt und den Code ein wenig unklarer macht, würde ich mich wahrscheinlich davor zurückhalten dies einem Freund zu überlassen, der gerade seinen SCJP erhalten hat und gutmütige Zweikämpfe über Java Semantik! ;-) liebt. Vielen Dank an alle!

7/2017: Baeldung hat eine gute Zusammenfassung der doppelten Klammerinitialisierung und betrachtet sie als Anti-Pattern.

12/2017: @Basil Bourque stellt fest, dass in der neuen Java 9 Sie sagen können:

Set<String> flavors = Set.of("Vanilla", "strawberry", "chocolate", "butter pecan");

Das ist mit Sicherheit der richtige Weg. Wenn Sie mit einer früheren Version nicht weiterkommen, schauen Sie sich ImmutableSet von Google Collections an.

770
Jim Ferrans

Hier ist das Problem, wenn ich mich zu sehr von anonymen inneren Klassen hinreißen lasse:

2009/05/27  16:35             1,602 DemoApp2$1.class
2009/05/27  16:35             1,976 DemoApp2$10.class
2009/05/27  16:35             1,919 DemoApp2$11.class
2009/05/27  16:35             2,404 DemoApp2$12.class
2009/05/27  16:35             1,197 DemoApp2$13.class

/* snip */

2009/05/27  16:35             1,953 DemoApp2$30.class
2009/05/27  16:35             1,910 DemoApp2$31.class
2009/05/27  16:35             2,007 DemoApp2$32.class
2009/05/27  16:35               926 DemoApp2$33$1$1.class
2009/05/27  16:35             4,104 DemoApp2$33$1.class
2009/05/27  16:35             2,849 DemoApp2$33.class
2009/05/27  16:35               926 DemoApp2$34$1$1.class
2009/05/27  16:35             4,234 DemoApp2$34$1.class
2009/05/27  16:35             2,849 DemoApp2$34.class

/* snip */

2009/05/27  16:35               614 DemoApp2$40.class
2009/05/27  16:35             2,344 DemoApp2$5.class
2009/05/27  16:35             1,551 DemoApp2$6.class
2009/05/27  16:35             1,604 DemoApp2$7.class
2009/05/27  16:35             1,809 DemoApp2$8.class
2009/05/27  16:35             2,022 DemoApp2$9.class

Dies sind alles Klassen, die erstellt wurden, als ich eine einfache Anwendung erstellte, und viele anonyme innere Klassen verwendeten - jede Klasse wird in eine separate class -Datei kompiliert.

Die "doppelte geschweifte Klammer" ist, wie bereits erwähnt, eine anonyme innere Klasse mit einem Instanzinitialisierungsblock, was bedeutet, dass für jede "Initialisierung" eine neue Klasse erstellt wird, um normalerweise ein einzelnes Objekt zu erstellen.

In Anbetracht dessen, dass die Java Virtual Machine alle Klassen lesen muss, wenn sie verwendet wird, kann dies zu einer gewissen Zeit im Bytecode-Verifizierung Prozess führen, und so weiter. Nicht zu erwähnen die Zunahme des benötigten Speicherplatzes, um alle diese class Dateien zu speichern.

Es scheint, als ob bei der Verwendung der doppelten Klammerinitialisierung ein gewisser Overhead entsteht. Daher ist es wahrscheinlich keine so gute Idee, die Initialisierung zu übertreiben. Aber wie Eddie in den Kommentaren angemerkt hat, ist es nicht möglich, sich der Auswirkungen absolut sicher zu sein.


Nur als Referenz ist die doppelte geschweifte Klammer die folgende:

List<String> list = new ArrayList<String>() {{
    add("Hello");
    add("World!");
}};

Es sieht aus wie ein "verstecktes" Feature von Java, aber es ist nur ein Umschreiben von:

List<String> list = new ArrayList<String>() {

    // Instance initialization block
    {
        add("Hello");
        add("World!");
    }
};

Es handelt sich also im Grunde genommen um einen Instanzinitialisierungsblock , der Teil einer anonymen inneren Klasse ist.


Joshua Blochs Vorschlag für Collection Literals für Project Coin lautete wie folgt:

List<Integer> intList = [1, 2, 3, 4];

Set<String> strSet = {"Apple", "Banana", "Cactus"};

Map<String, Integer> truthMap = { "answer" : 42 };

Leider ist es hat seinen Weg nicht gefunden in weder Java 7 noch 8 und wurde auf unbestimmte Zeit zurückgestellt.


Experiment

Hier ist das einfache Experiment, das ich getestet habe: Machen Sie 1000 ArrayLists mit den Elementen "Hello" Und "World!", Die ihnen über die Methode add hinzugefügt wurden Methoden:

Methode 1: Double Brace Initialization

List<String> l = new ArrayList<String>() {{
  add("Hello");
  add("World!");
}};

Methode 2: Instanziiere ein ArrayList und ein add

List<String> l = new ArrayList<String>();
l.add("Hello");
l.add("World!");

Ich habe ein einfaches Programm erstellt, um eine Java Quelldatei zu schreiben, um 1000 Initialisierungen mit den beiden Methoden durchzuführen:

Test 1:

class Test1 {
  public static void main(String[] s) {
    long st = System.currentTimeMillis();

    List<String> l0 = new ArrayList<String>() {{
      add("Hello");
      add("World!");
    }};

    List<String> l1 = new ArrayList<String>() {{
      add("Hello");
      add("World!");
    }};

    /* snip */

    List<String> l999 = new ArrayList<String>() {{
      add("Hello");
      add("World!");
    }};

    System.out.println(System.currentTimeMillis() - st);
  }
}

Test 2:

class Test2 {
  public static void main(String[] s) {
    long st = System.currentTimeMillis();

    List<String> l0 = new ArrayList<String>();
    l0.add("Hello");
    l0.add("World!");

    List<String> l1 = new ArrayList<String>();
    l1.add("Hello");
    l1.add("World!");

    /* snip */

    List<String> l999 = new ArrayList<String>();
    l999.add("Hello");
    l999.add("World!");

    System.out.println(System.currentTimeMillis() - st);
  }
}

Bitte beachten Sie, dass die verstrichene Zeit zum Initialisieren der 1000 ArrayLists und der 1000 anonymen inneren Klassen, die ArrayList erweitern, mit dem System.currentTimeMillis Überprüft wird, sodass der Zeitgeber keinen sehr hohen Wert hat Auflösung. Auf meinem Windows-System beträgt die Auflösung etwa 15-16 Millisekunden.

Die Ergebnisse für 10 Läufe der beiden Tests waren die folgenden:

Test1 Times (ms)           Test2 Times (ms)
----------------           ----------------
           187                          0
           203                          0
           203                          0
           188                          0
           188                          0
           187                          0
           203                          0
           188                          0
           188                          0
           203                          0

Wie zu sehen ist, hat die doppelte geschweifte Klammer eine merkliche Ausführungszeit von ungefähr 190 ms.

In der Zwischenzeit betrug die Ausführungszeit für die ArrayList -Initialisierung 0 ms. Natürlich sollte die Timer-Auflösung berücksichtigt werden, aber sie liegt wahrscheinlich unter 15 ms.

Es scheint also einen merklichen Unterschied in der Ausführungszeit der beiden Methoden zu geben. Es scheint, dass die beiden Initialisierungsmethoden tatsächlich einen gewissen Overhead verursachen.

Und ja, es gab 1000 .class - Dateien, die beim Kompilieren des Test1 - Doppelklammer-Initialisierungstestprogramms generiert wurden.

581
coobird

Eine Eigenschaft dieses Ansatzes, auf die bisher nicht hingewiesen wurde, ist, dass die gesamte enthaltende Klasse in ihrem Bereich erfasst wird, weil Sie innere Klassen erstellen. Dies bedeutet, dass Ihr Set, solange es aktiv ist, einen Zeiger auf die enthaltende Instanz (this$0) Behält und verhindert, dass dieser vom Müll gesammelt wird, was ein Problem sein könnte.

Dies und die Tatsache, dass in erster Linie eine neue Klasse erstellt wird, obwohl ein reguläres HashSet einwandfrei (oder sogar besser) funktionieren würde, lässt mich dieses Konstrukt nicht verwenden (obwohl ich mich wirklich nach dem syntaktischen Zucker sehne).

Zweite Frage: Das neue HashSet muss das "this" sein, das im Instanzinitialisierer verwendet wird ... kann jemand Licht in den Mechanismus bringen? Ich hätte naiv erwartet, dass "this" sich auf das Objekt bezieht, das "Flavours" initialisiert.

So funktionieren innere Klassen. Sie erhalten ihr eigenes this, haben aber auch Zeiger auf die übergeordnete Instanz, sodass Sie auch Methoden für das übergeordnete Objekt aufrufen können. Im Falle eines Namenskonflikts hat die innere Klasse (in Ihrem Fall HashSet) Vorrang, aber Sie können "this" einen Klassennamen voranstellen, um auch die äußere Methode abzurufen.

public class Test {

    public void add(Object o) {
    }

    public Set<String> makeSet() {
        return new HashSet<String>() {
            {
              add("hello"); // HashSet
              Test.this.add("hello"); // outer instance 
            }
        };
    }
}

Um zu verdeutlichen, welche anonyme Unterklasse erstellt wird, können Sie dort auch Methoden definieren. Zum Beispiel überschreiben HashSet.add()

    public Set<String> makeSet() {
        return new HashSet<String>() {
            {
              add("hello"); // not HashSet anymore ...
            }

            @Override
            boolean add(String s){

            }

        };
    }
98
Thilo

Jedes Mal, wenn jemand die Initialisierung in doppelter Klammer verwendet, wird ein Kätzchen getötet.

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

1. Sie erstellen viel zu viele anonyme Klassen

Jedes Mal, wenn Sie die doppelte geschweifte Klammer verwenden, wird eine neue Klasse erstellt. Z.B. 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 sie einmal ausführen. Aber wenn Sie dies 20'000 Mal in Ihrer gesamten Unternehmensanwendung tun ... all den Haufen 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 die Aufrufer dieser Methode möglicherweise ahnungslos an sehr umfangreichen Ressourcen fest, die nicht durch Garbage-Collection 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;
    }
}

Das zurückgegebene Map enthält nun einen Verweis auf die einschließende Instanz von ReallyHeavyObject. Das wollen Sie wahrscheinlich nicht riskieren:

Memory Leak Right Here

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 ob Java hat Kartenliterale

Um Ihre eigentliche Frage zu beantworten, haben die Leute diese Syntax verwendet, um vorzutäuschen, dass Java hat so etwas wie Map-Literale, ähnlich wie die vorhandenen Array-Literale:

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

Einige Leute mögen dies syntaktisch anregend finden.

47
Lukas Eder

leckanfällig

Ich habe beschlossen, mich einzuschalten. Die Auswirkungen auf die Leistung umfassen: Festplattenbetrieb + Entpacken (für JAR), Klassenüberprüfung, Speicherplatz (für Suns Hotspot JVM). Das Schlimmste ist jedoch, dass es leckanfällig ist. Sie können nicht einfach zurückkehren.

Set<String> getFlavors(){
  return Collections.unmodifiableSet(flavors)
}

Wenn die Menge also zu einem anderen Teil gelangt, der von einem anderen Klassenladeprogramm geladen wurde, und dort eine Referenz gespeichert wird, wird der gesamte Baum von Klassen + Klassenladeprogramm gelöscht. Um dies zu vermeiden, ist eine Kopie nach HashMap erforderlich, new LinkedHashSet(new ArrayList(){{add("xxx);add("yyy");}}). Nicht mehr so ​​süß. Ich selbst verwende das Idiom nicht, sondern es ist wie new LinkedHashSet(Arrays.asList("xxx","YYY"));

35
bestsss

Folgende Testklasse belegen:

public class Test {
  public void test() {
    Set<String> flavors = new HashSet<String>() {{
        add("Vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
    }};
  }
}

und dann die Klassendatei dekompilieren, sehe ich:

public class Test {
  public void test() {
    Java.util.Set flavors = new HashSet() {

      final Test this$0;

      {
        this$0 = Test.this;
        super();
        add("Vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
      }
    };
  }
}

Das sieht für mich nicht sonderlich ineffizient aus. Wenn ich mir für so etwas Sorgen um die Leistung machen würde, würde ich es mir vorstellen. Und Ihre Frage Nr. 2 wird durch den obigen Code beantwortet: Sie befinden sich in einem impliziten Konstruktor (und einem Instanzinitialisierer) für Ihre innere Klasse, sodass "this" auf diese innere Klasse verweist.

Ja, diese Syntax ist unklar, aber ein Kommentar kann die unklare Syntaxverwendung verdeutlichen. Um die Syntax zu verdeutlichen, kennen die meisten Benutzer einen statischen Initialisierungsblock (JLS 8.7 Static Initializers):

public class Sample1 {
    private static final String someVar;
    static {
        String temp = null;
        ..... // block of code setting temp
        someVar = temp;
    }
}

Sie können auch eine ähnliche Syntax (ohne das Wort "static") für die Konstruktorverwendung (JLS 8.6-Instanzinitialisierer) verwenden, obwohl ich diese Syntax noch nie im Produktionscode verwendet habe. Dies ist viel weniger bekannt.

public class Sample2 {
    private final String someVar;

    // This is an instance initializer
    {
        String temp = null;
        ..... // block of code setting temp
        someVar = temp;
    }
}

Wenn Sie keinen Standardkonstruktor haben, wird der Codeblock zwischen { und } wird vom Compiler in einen Konstruktor umgewandelt. Entschlüsseln Sie vor diesem Hintergrund den doppelten Klammercode:

public void test() {
  Set<String> flavors = new HashSet<String>() {
      {
        add("Vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
      }
  };
}

Der Codeblock zwischen den innersten Klammern wird vom Compiler in einen Konstruktor umgewandelt. Die äußersten Klammern begrenzen die anonyme innere Klasse. Um dies zum letzten Schritt zu machen, machen Sie alles nicht anonym:

public void test() {
  Set<String> flavors = new MyHashSet();
}

class MyHashSet extends HashSet<String>() {
    public MyHashSet() {
        add("Vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
    }
}

Zu Initialisierungszwecken würde ich sagen, dass es überhaupt keinen Overhead gibt (oder so klein, dass er vernachlässigt werden kann). Jedoch wird jede Verwendung von flavors nicht gegen HashSet gehen, sondern gegen MyHashSet. Dies ist wahrscheinlich mit einem geringen (und möglicherweise vernachlässigbaren) Aufwand verbunden. Aber noch einmal, bevor ich mir Sorgen machte, würde ich es mir vorstellen.

Wiederum ist der obige Code für Ihre Frage Nr. 2 das logische und explizite Äquivalent der doppelten geschweiften Klammer und macht deutlich, wo sich "this" bezieht: Auf die innere Klasse, die HashSet erweitert. .

Wenn Sie Fragen zu den Details der Instanzinitialisierer haben, lesen Sie die Details in der Dokumentation JLS .

35
Eddie

Durch das Laden vieler Klassen können einige Millisekunden zum Start hinzugefügt werden. Wenn der Start nicht so kritisch ist und Sie sich die Effizienz von Klassen nach dem Start ansehen, gibt es keinen Unterschied.

package Vanilla.Java.perfeg.doublebracket;

import Java.util.*;

/**
 * @author plawrey
 */
public class DoubleBracketMain {
    public static void main(String... args) {
        final List<String> list1 = new ArrayList<String>() {
            {
                add("Hello");
                add("World");
                add("!!!");
            }
        };
        List<String> list2 = new ArrayList<String>(list1);
        Set<String> set1 = new LinkedHashSet<String>() {
            {
                addAll(list1);
            }
        };
        Set<String> set2 = new LinkedHashSet<String>();
        set2.addAll(list1);
        Map<Integer, String> map1 = new LinkedHashMap<Integer, String>() {
            {
                put(1, "one");
                put(2, "two");
                put(3, "three");
            }
        };
        Map<Integer, String> map2 = new LinkedHashMap<Integer, String>();
        map2.putAll(map1);

        for (int i = 0; i < 10; i++) {
            long dbTimes = timeComparison(list1, list1)
                    + timeComparison(set1, set1)
                    + timeComparison(map1.keySet(), map1.keySet())
                    + timeComparison(map1.values(), map1.values());
            long times = timeComparison(list2, list2)
                    + timeComparison(set2, set2)
                    + timeComparison(map2.keySet(), map2.keySet())
                    + timeComparison(map2.values(), map2.values());
            if (i > 0)
                System.out.printf("double braced collections took %,d ns and plain collections took %,d ns%n", dbTimes, times);
        }
    }

    public static long timeComparison(Collection a, Collection b) {
        long start = System.nanoTime();
        int runs = 10000000;
        for (int i = 0; i < runs; i++)
            compareCollections(a, b);
        long rate = (System.nanoTime() - start) / runs;
        return rate;
    }

    public static void compareCollections(Collection a, Collection b) {
        if (!a.equals(b) && a.hashCode() != b.hashCode() && !a.toString().equals(b.toString()))
            throw new AssertionError();
    }
}

druckt

double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 34 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
19
Peter Lawrey

Zum Erstellen von Sets können Sie eine varargs-Factory-Methode anstelle der doppelten Klammerinitialisierung verwenden:

public static Set<T> setOf(T ... elements) {
    return new HashSet<T>(Arrays.asList(elements));
}

Die Google Collections-Bibliothek bietet viele praktische Methoden wie diese sowie eine Vielzahl weiterer nützlicher Funktionen.

Die Unbekanntheit der Redewendung begegnet mir und ich verwende sie die ganze Zeit im Produktionscode. Ich mache mir mehr Sorgen um Programmierer, die durch die Redewendung, Produktionscode schreiben zu dürfen, verwirrt sind.

16
Nat

Abgesehen von der Effizienz wünsche ich mir nur selten eine deklarative Kollektionserstellung außerhalb von Komponententests. Ich glaube, dass die doppelte Klammer-Syntax sehr gut lesbar ist.

Eine andere Möglichkeit, die deklarative Konstruktion von Listen zu erreichen, besteht darin, Arrays.asList(T ...) wie folgt zu verwenden:

List<String> aList = Arrays.asList("Vanilla", "strawberry", "chocolate");

Die Einschränkung dieses Ansatzes besteht natürlich darin, dass Sie den zu generierenden Listentyp nicht steuern können.

9
Paul Morie

Es gibt im Allgemeinen nichts besonders ineffizientes. Für die JVM spielt es im Allgemeinen keine Rolle, dass Sie eine Unterklasse erstellt und einen Konstruktor hinzugefügt haben. Dies ist in einer objektorientierten Sprache eine normale, alltägliche Aufgabe. Ich kann mir durchaus ausgedachte Fälle vorstellen, in denen Sie auf diese Weise eine Ineffizienz verursachen könnten (z. B. haben Sie eine wiederholt aufgerufene Methode, die aufgrund dieser Unterklasse eine Mischung aus verschiedenen Klassen belegt, während die übergebene Klasse normalerweise völlig vorhersehbar wäre). - Im letzteren Fall kann der JIT-Compiler Optimierungen vornehmen, die im ersten Fall nicht möglich sind. Aber ich denke wirklich, dass die Fälle, in denen es darauf ankommt, sehr künstlich sind.

Ich würde das Problem eher unter dem Gesichtspunkt sehen, ob Sie mit vielen anonymen Klassen "Unordnung" schaffen wollen. Als grobe Richtlinie sollten Sie in Betracht ziehen, nicht mehr als anonyme Klassen für Event-Handler zu verwenden.

In (2) befinden Sie sich im Konstruktor eines Objekts. "This" bezieht sich also auf das Objekt, das Sie erstellen. Das ist kein Unterschied zu anderen Konstruktoren.

Was (3) betrifft, hängt das wirklich davon ab, wer Ihren Code verwaltet, denke ich. Wenn Sie dies nicht im Voraus wissen, ist ein Benchmark, den ich empfehlen würde, "Sehen Sie dies im Quellcode des JDK?" (In diesem Fall kann ich mich nicht erinnern, viele anonyme Initialisierer gesehen zu haben, und schon gar nicht in Fällen, in denen dies der einzige Inhalt der anonymen Klasse ist.) In den meisten mittelgroßen Projekten müssen Ihre Programmierer die JDK-Quelle zu einem bestimmten Zeitpunkt wirklich verstehen. Daher ist jede dort verwendete Syntax oder Ausdrucksweise "faires Spiel". Darüber hinaus, würde ich sagen, schulen Sie die Leute in dieser Syntax, wenn Sie die Kontrolle darüber haben, wer den Code verwaltet, sonst kommentieren oder vermeiden.

7
Neil Coffey

Die doppelte geschweifte Initialisierung ist ein unnötiger Hack, der zu Speicherverlusten und anderen Problemen führen kann

Es gibt keinen legitimen Grund, diesen "Trick" anzuwenden. Guava bietet Nice nveränderliche Sammlungen , das sowohl statische Fabriken als auch Builder enthält, sodass Sie Ihre Sammlung dort einfügen können, wo sie in einer sauberen, lesbaren und sicheren Syntax deklariert ist.

Das Beispiel in der Frage wird:

Set<String> flavors = ImmutableSet.of(
    "Vanilla", "strawberry", "chocolate", "butter pecan");

Dies ist nicht nur kürzer und einfacher zu lesen, sondern vermeidet auch die zahlreichen Probleme mit dem in andere Antworten beschriebenen doppelstrebigen Muster. Sicher, es funktioniert ähnlich wie ein direkt konstruiertes HashMap, aber es ist gefährlich und fehleranfällig, und es gibt bessere Optionen.

Jedes Mal, wenn Sie über eine doppelte Initialisierung nachdenken, sollten Sie Ihre APIs erneut untersuchen oder neue einführen , um das Problem richtig anzugehen, anstatt syntaktische Tricks zu nutzen.

fehleranfällig now kennzeichnet dieses Anti-Pattern .

5
dimo414

Ich habe dies untersucht und mich entschlossen, einen tiefergehenden Test als den durch die gültige Antwort bereitgestellten durchzuführen.

Hier ist der Code: https://Gist.github.com/4368924

und das ist meine Schlussfolgerung

Ich war überrascht, dass die interne Initiierung in den meisten Testläufen tatsächlich schneller war (in einigen Fällen fast doppelt so hoch). Bei der Arbeit mit großen Zahlen scheint der Nutzen nachzulassen.

Interessanterweise geht der Vorteil eines Falls, der 3 Objekte in der Schleife erstellt, schneller verloren als in den anderen Fällen. Ich bin nicht sicher, warum dies geschieht, und es sollten weitere Tests durchgeführt werden, um zu Schlussfolgerungen zu gelangen. Das Erstellen konkreter Implementierungen kann dazu beitragen, dass die Klassendefinition nicht erneut geladen werden muss (sofern dies der Fall ist).

Es ist jedoch klar, dass selbst bei großen Stückzahlen in den meisten Fällen für den Einzelpostenbau nicht viel Overhead zu verzeichnen ist.

Ein Nachteil wäre die Tatsache, dass jede der doppelten Klammern eine neue Klassendatei erstellt, die der Größe unserer Anwendung einen ganzen Plattenblock hinzufügt (oder etwa 1 KB, wenn sie komprimiert ist). Ein kleiner Platzbedarf, der sich jedoch möglicherweise auswirken kann, wenn er an vielen Orten verwendet wird. Verwenden Sie diese 1000-mal, und Sie fügen Ihrer Anwendung möglicherweise eine ganze MB hinzu, was in einer eingebetteten Umgebung von Belang sein kann.

Meine Schlussfolgerung? Es kann in Ordnung sein, es zu verwenden, solange es nicht missbraucht wird.

Lass mich wissen was du denkst :)

4
pablisco

Ich übernehme die Antwort von Nat, außer dass ich eine Schleife verwenden würde, anstatt die implizite Liste aus asList (elements) zu erstellen und sofort zu werfen:

static public Set<T> setOf(T ... elements) {
    Set set=new HashSet<T>(elements.size());
    for(T Elm: elements) { set.add(Elm); }
    return set;
    }
3
Lawrence Dol

Diese Syntax kann zwar praktisch sein, fügt jedoch eine Menge dieser $ 0-Referenzen hinzu, da diese verschachtelt werden, und es kann schwierig sein, die Initialisierer schrittweise zu debuggen, es sei denn, für jeden werden Haltepunkte festgelegt. Aus diesem Grund empfehle ich die Verwendung nur für banale Setter, insbesondere für Konstanten und Orte, an denen anonyme Unterklassen keine Rolle spielen (z. B. ohne Serialisierung).

3
Eric Woodruff

Mario Gleichman beschreibt wie man Java 1.5 generische Funktionen zur Simulation verwendet Scala Listen Sie Literale auf, obwohl Sie leider mit enden -immutable Listen.

Er definiert diese Klasse:

package literal;

public class collection {
    public static <T> List<T> List(T...elems){
        return Arrays.asList( elems );
    }
}

und benutzt es so:

import static literal.collection.List;
import static system.io.*;

public class CollectionDemo {
    public void demoList(){
        List<String> slist = List( "a", "b", "c" );
        List<Integer> iList = List( 1, 2, 3 );
        for( String elem : List( "a", "Java", "list" ) )
            System.out.println( elem );
    }
}

Google Collections, jetzt Teil von Guava , unterstützt eine ähnliche Idee für die Listenerstellung. In diesem Interview sagt Jared Levy:

[...] Die am häufigsten verwendeten Funktionen, die in fast jeder von mir geschriebenen Java= - Klasse vorkommen, sind statische Methoden, die die Anzahl der wiederholten Tastenanschläge in Ihrem Java) - Code verringern === Code Es ist so praktisch, Befehle wie den folgenden eingeben zu können:

Map<OneClassWithALongName, AnotherClassWithALongName> = Maps.newHashMap();

List<String> animals = Lists.immutableList("cat", "dog", "horse");

10.07.2014: Wenn es nur so einfach sein könnte wie bei Python:

animals = ['cat', 'dog', 'horse']

3
Jim Ferrans
  1. Dies ruft add() für jedes Mitglied auf. Wenn Sie eine effizientere Möglichkeit finden, Elemente in ein Hash-Set einzufügen, verwenden Sie diese. Beachten Sie, dass die innere Klasse wahrscheinlich Müll erzeugt, wenn Sie diesbezüglich sensibel sind.

  2. Es scheint mir, als ob der Kontext das Objekt ist, das von new zurückgegeben wird, das HashSet ist.

  3. Wenn Sie fragen müssen ... Wahrscheinlicher: Werden die Leute, die nach Ihnen kommen, das wissen oder nicht? Ist es leicht zu verstehen und zu erklären? Wenn Sie beide Fragen mit "Ja" beantworten können, können Sie sie gerne verwenden.

2
Jon Watte