908 Stimmen

Effizienz der Java "Double Brace Initialisierung"?

En Versteckte Funktionen von Java die erste Antwort erwähnt Initialisierung der doppelten Klammer , mit einer muy verlockende Syntax:

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

Dieses Idiom erzeugt eine anonyme innere Klasse, die nur einen Instanzinitialisierer enthält, der "alle [...] Methoden im enthaltenen Bereich verwenden kann".

Die wichtigste Frage: Ist das so ineffizient wie es klingt? Sollte ihre Verwendung auf einmalige Initialisierungen beschränkt werden? (Und natürlich auf Angeberei!)

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

Dritte Frage: Ist diese Redewendung zu undurchsichtig im Produktionscode zu verwenden?

Zusammenfassung: Sehr, sehr gute Antworten, vielen Dank an alle. Bei Frage (3) waren die Leute der Meinung, dass die Syntax klar sein sollte (obwohl ich einen gelegentlichen Kommentar empfehlen würde, vor allem, wenn Ihr Code an Entwickler weitergegeben wird, die damit vielleicht nicht vertraut sind).

Bei Frage (1) sollte der generierte Code schnell laufen. Die zusätzlichen .class-Dateien verursachen Unordnung in den jar-Dateien und verlangsamen den Programmstart etwas (Dank an @coobird für die Messung). @Thilo wies darauf hin, dass die Garbage Collection beeinträchtigt werden kann, und die Speicherkosten für die zusätzlich geladenen Klassen können in einigen Fällen ein Faktor sein.

Die Frage (2) war für mich am interessantesten. Wenn ich die Antworten verstehe, passiert in DBI, dass die anonyme innere Klasse die Klasse des Objekts erweitert, das durch den new-Operator konstruiert wird, und daher einen "this"-Wert hat, der auf die konstruierte Instanz verweist. Sehr ordentlich.

Insgesamt erscheint mir die DBI als eine Art intellektuelle Kuriosität. Coobird und andere weisen darauf hin, dass man denselben Effekt mit Arrays.asList, varargs-Methoden, Google Collections und den vorgeschlagenen Java 7 Collection-Literalen erzielen kann. Neuere JVM-Sprachen wie Scala, JRuby und Groovy bieten ebenfalls prägnante Notationen für die Listenkonstruktion und arbeiten gut mit Java zusammen. In Anbetracht der Tatsache, dass DBI den Klassenpfad unübersichtlich macht, das Laden von Klassen etwas verlangsamt und den Code ein wenig undurchsichtiger macht, würde ich es wahrscheinlich meiden. Allerdings habe ich vor, dies einem Freund zu empfehlen, der gerade seinen SCJP gemacht hat und sich gerne über die Java-Semantik streitet ;-) Vielen Dank an alle!

7/2017: Baeldung hat eine gute Zusammenfassung der doppelten Klammerinitialisierung und hält sie für ein Anti-Muster.

12/2017: @Basil Bourque stellt fest, dass man im neuen Java 9 sagen kann:

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

Das ist mit Sicherheit der richtige Weg. Wenn Sie mit einer früheren Version arbeiten müssen, schauen Sie sich folgende Seiten an Google Collections' ImmutableSet .

3voto

Lawrence Dol Punkte 61053

Ich schließe mich der Antwort von Nat an, außer dass ich eine Schleife verwenden würde, anstatt die implizite Liste aus asList(elements) zu erstellen und sofort zu verwerfen:

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

3voto

Jim Ferrans Punkte 29952

Mario Gleichmann beschreibt wie man generische Java 1.5-Funktionen verwendet, um Scala-Listenliterale zu simulieren, obwohl man dabei leider mit unveränderlich Verzeichnisse.

Er definiert diese Klasse:

package literal;

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

und verwendet es auf diese Weise:

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 Guave unterstützt eine ähnliche Idee für den Listenaufbau. Unter dieses 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 sich wiederholenden Tastenanschläge in Ihrem Java-Code reduzieren. 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");

7/10/2014: Wenn es nur so einfach wäre wie bei Python:

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

2/21/2020: In Java 11 kann man jetzt sagen:

animals = List.of(“cat”, “dog”, “horse”)

3voto

pablisco Punkte 13542

Ich habe nachgeforscht und beschlossen, einen gründlicheren Test durchzuführen als den, der in der gültigen Antwort angegeben ist.

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

und dies ist meine Schlussfolgerung

Ich war überrascht, dass bei den meisten Testläufen der interne Anstoß tatsächlich schneller war (in einigen Fällen fast doppelt so schnell). Bei der Arbeit mit großen Zahlen scheint der Vorteil zu schwinden.

Interessanterweise verliert der Fall, in dem 3 Objekte in der Schleife erzeugt werden, seinen Nutzen früher als in den anderen Fällen. Ich bin mir nicht sicher, warum dies der Fall ist, und es sollten weitere Tests durchgeführt werden, um Schlussfolgerungen zu ziehen. Die Erstellung konkreter Implementierungen kann helfen, das Neuladen der Klassendefinition zu vermeiden (falls dies der Fall ist).

Es ist jedoch klar, dass in den meisten Fällen kein großer Overhead für das Einzelpostengebäude zu beobachten ist, selbst bei großen Zahlen.

Ein Rückschlag wäre die Tatsache, dass jede der doppelten Klammerinitiierungen eine neue Klassendatei erzeugt, die die Größe unserer Anwendung um einen ganzen Festplattenblock erhöht (oder etwa 1k, wenn sie komprimiert wird). Ein kleiner Fußabdruck, aber wenn es in vielen Orten verwendet wird, könnte es möglicherweise eine Auswirkung haben. Wenn Sie dies 1000 Mal verwenden, können Sie Ihrer Anwendung eine ganze MiB hinzufügen, was in einer eingebetteten Umgebung von Bedeutung sein kann.

Mein Fazit? Es kann in Ordnung sein, es zu benutzen, solange es nicht missbraucht wird.

Lassen Sie mich wissen, was Sie denken :)

3voto

Eric Woodruff Punkte 6134

Während diese Syntax bequem sein kann, fügt sie auch eine Menge this$0-Referenzen hinzu, da diese verschachtelt werden und es schwierig sein kann, in die Initialisierer zu debuggen, es sei denn, es werden Haltepunkte für jeden einzelnen gesetzt. Aus diesem Grund empfehle ich, dies nur für banale Setter zu verwenden, vor allem auf Konstanten gesetzt, und an Orten, wo anonyme Unterklassen keine Rolle spielen (wie keine Serialisierung beteiligt).

2voto

  1. Dies führt zum Aufruf add() für jedes Mitglied. Wenn Sie einen effizienteren Weg finden, um Elemente in ein Hash-Set zu setzen, dann verwenden Sie diesen. Beachten Sie, dass die innere Klasse wahrscheinlich Müll erzeugen wird, wenn Sie diesbezüglich empfindlich sind.

  2. Es scheint mir, als ob der Kontext das Objekt ist, das von new das ist die HashSet .

  3. Wenn Sie fragen müssen... Eher: 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 es gerne verwenden.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X