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 .

19voto

Peter Lawrey Punkte 511323

Das Laden vieler Klassen kann den Start um einige Millisekunden verzögern. Wenn der Start nicht so kritisch ist und man sich die Effizienz der Klassen nach dem Start ansieht, 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

16voto

Nat Punkte 9604

Um Sets zu erstellen, können Sie eine varargs-Factory-Methode anstelle der Double-Brace-Initialisierung verwenden:

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

Die Google-Collections-Bibliothek verfügt über viele praktische Methoden wie diese sowie über eine Vielzahl anderer nützlicher Funktionen.

Was die Unklarheit des Idioms angeht, so begegne ich ihm ständig und verwende es im Produktionscode. Ich würde mir mehr Sorgen darüber machen, dass Programmierer, die durch das Idiom verwirrt werden, Produktionscode schreiben dürfen.

11voto

Paul Morie Punkte 15078

Abgesehen von der Effizienz wünsche ich mir selten eine deklarative Erstellung von Sammlungen außerhalb von Unit-Tests. Ich glaube, dass die Syntax der doppelten Klammer sehr lesbar ist.

Eine andere Möglichkeit, die deklarative Konstruktion von Listen zu erreichen, ist die Verwendung von Arrays.asList(T ...) etwa so:

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

Die Einschränkung dieses Ansatzes besteht natürlich darin, dass Sie den spezifischen Typ der zu erstellenden Liste nicht kontrollieren können.

7voto

dimo414 Punkte 44554

Double-Brace-Initialisierung ist ein unnötiger Hack, der zu Speicherlecks und anderen Problemen führen kann

Es gibt keinen legitimen Grund, diesen "Trick" zu verwenden. Guava bietet nette unveränderliche Sammlungen die sowohl statische Fabriken als auch Builder enthalten, so dass Sie Ihre Sammlung dort auffüllen können, wo sie in einer sauberen, lesbaren und sicher Syntax.

Das Beispiel in der Frage wird:

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

Dies ist nicht nur kürzer und leichter zu lesen, sondern vermeidet auch die zahlreichen Probleme, die mit dem doppelstrebigen Muster verbunden sind, das in andere Antworten . Sicher, sie funktioniert ähnlich wie eine direkt konstruierte HashMap aber es ist gefährlich und fehleranfällig, und es gibt bessere Möglichkeiten.

Jedes Mal, wenn Sie eine doppelt verankerte Initialisierung in Erwägung ziehen, sollten Sie Ihre APIs überprüfen oder neue einführen um das Problem richtig anzugehen, anstatt sich syntaktische Tricks zunutze zu machen.

Fehleranfällig jetzt kennzeichnet dieses Anti-Muster .

6voto

Neil Coffey Punkte 21238

Das ist im Allgemeinen nicht besonders ineffizient. Für die JVM spielt es im Allgemeinen keine Rolle, dass Sie eine Unterklasse erstellt und ihr einen Konstruktor hinzugefügt haben - das ist eine normale, alltägliche Sache, die man in einer objektorientierten Sprache tut. Ich kann mir ziemlich ausgeklügelte Fälle vorstellen, in denen dies zu einer Ineffizienz führen könnte (z.B. eine wiederholt aufgerufene Methode, die aufgrund dieser Unterklasse eine Mischung verschiedener Klassen annimmt, während die übergebene Klasse normalerweise völlig vorhersehbar wäre - im letzteren Fall könnte der JIT-Compiler Optimierungen vornehmen, die im ersten Fall nicht möglich sind). Aber ich denke, dass die Fälle, in denen das eine Rolle spielt, sehr konstruiert sind.

Ich würde das Problem eher unter dem Gesichtspunkt sehen, ob Sie die Dinge mit vielen anonymen Klassen "überladen" wollen. Als grober Anhaltspunkt sollten Sie das Idiom nicht mehr verwenden, als Sie anonyme Klassen für Event-Handler verwenden würden.

In (2) befinden Sie sich im Konstruktor eines Objekts, also bezieht sich "this" auf das Objekt, das Sie konstruieren. Das ist nicht anders als bei jedem anderen Konstruktor.

Was (3) betrifft, so hängt das wirklich davon ab, wer Ihren Code pflegt, denke ich. Wenn Sie das nicht im Voraus wissen, dann würde ich vorschlagen, einen Benchmark zu verwenden: "Sieht man das im Quellcode des JDK?" (in diesem Fall kann ich mich nicht daran erinnern, viele anonyme Initialisierer gesehen zu haben, und schon gar nicht in Fällen, in denen dies der Fall ist. ばかり Inhalt der anonymen Klasse). In den meisten mittelgroßen Projekten müssen Ihre Programmierer den JDK-Quelltext verstehen, so dass jede dort verwendete Syntax oder Redewendung "Freiwild" ist. Darüber hinaus würde ich sagen, schulen Sie die Leute in dieser Syntax, wenn Sie die Kontrolle darüber haben, wer den Code pflegt, sonst kommentieren oder vermeiden Sie es.

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