with - list of objects java




Efficacité de Java "Initialisation Double Brace"? (10)

Dans Hidden Features of Java, la réponse la plus haute mentionne l' initialisation Double Brace , avec une syntaxe très séduisante:

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

Cet idiome crée une classe interne anonyme avec juste un initialiseur d'instance, qui "peut utiliser n'importe [...] méthodes dans la portée contenant".

Question principale: Est-ce aussi inefficace que cela puisse paraître? Son utilisation devrait-elle être limitée à des initialisations uniques? (Et bien sûr montrer!)

Deuxième question: Le nouveau HashSet doit être le "this" utilisé dans l'initialiseur d'instance ... quelqu'un peut-il faire la lumière sur le mécanisme?

Troisième question: Cet idiome est-il trop obscur pour être utilisé dans le code de production?

Résumé: Très, très belles réponses, merci à tous. À la question (3), les gens pensaient que la syntaxe devrait être claire (bien que je recommande un commentaire occasionnel, surtout si votre code sera transmis à des développeurs qui ne le connaissent peut-être pas).

À la question (1), le code généré devrait fonctionner rapidement. Les fichiers .class supplémentaires provoquent un encombrement du fichier jar et ralentissent légèrement le démarrage du programme (grâce à @coobird pour mesurer cela). @Thilo a souligné que la récupération de place peut être affectée, et le coût de la mémoire pour les classes surchargées peut être un facteur dans certains cas.

La question (2) s'est avérée être la plus intéressante pour moi. Si je comprends les réponses, ce qui se passe dans DBI est que la classe interne anonyme étend la classe de l'objet en cours de construction par le nouvel opérateur, et a donc une valeur "this" référençant l'instance en cours de construction. Très propre.

Dans l'ensemble, DBI me semble être une sorte de curiosité intellectuelle. Coobird et d'autres soulignent que vous pouvez obtenir le même effet avec Arrays.asList, les méthodes varargs, Google Collections et les littéraux de la collection Java 7 proposés. Les langages JVM plus récents comme Scala, JRuby et Groovy offrent également des notations concises pour la construction de listes, et interfèrent bien avec Java. Étant donné que DBI encombre le classpath, ralentit un peu le chargement de la classe, et rend le code un peu plus obscur, j'y penserais probablement. Cependant, je prévois de lancer ceci sur un ami qui vient d'obtenir son SCJP et aime les joutes de bonne nature sur la sémantique de Java! ;-) Merci tout le monde!

7/2017: Baeldung a un bon résumé de l'initialisation double accolade et le considère comme un anti-pattern.

12/2017: @Basil Bourque note que dans le nouveau Java 9, vous pouvez dire:

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

C'est sûr le chemin à parcourir. Si vous êtes bloqué avec une version antérieure, jetez un oeil à ImmutableSet de Google Collections .


Double-brace initialization is an unnecessary hack that can introduce memory leaks and other issues

There's no legitimate reason to use this "trick". Guava provides nice immutable collections that include both static factories and builders, allowing you to populate your collection where it's declared in a clean, readable, and safe syntax.

The example in the question becomes:

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

Not only is this shorter and easier to read, but it avoids the numerous issues with the double-braced pattern described in other answers . Sure, it performs similarly to a directly-constructed HashMap , but it's dangerous and error-prone, and there are better options.

Any time you find yourself considering double-braced initialization you should re-examine your APIs or introduce new ones to properly address the issue, rather than take advantage of syntactic tricks.

Error-Prone now flags this anti-pattern .


Bien que cette syntaxe puisse être pratique, elle ajoute également beaucoup de ces références $ 0 car celles-ci deviennent imbriquées et il peut être difficile d'exécuter le débogage dans les initialiseurs à moins que des points de rupture ne soient définis sur chacun d'entre eux. For that reason, I only recommend using this for banal setters, especially set to constants, and places where anonymous subclasses don't matter (like no serialization involved).


Je faisais des recherches et j'ai décidé de faire un test plus approfondi que celui fourni par la réponse valide.

Voici le code: https://gist.github.com/4368924

et ceci est ma conclusion

J'ai été surpris de constater que dans la plupart des tests, l'initiation interne était en réalité plus rapide (presque le double dans certains cas). Lorsque vous travaillez avec de grands nombres, le bénéfice semble s'estomper.

Il est intéressant de noter que le cas qui crée 3 objets sur la boucle perd son bénéfice plus tôt que dans les autres cas. Je ne suis pas sûr pourquoi cela se produit et plus d'essais devraient être faits pour arriver à des conclusions. Créer des implémentations concrètes peut aider à éviter que la définition de la classe soit rechargée (si c'est ce qui se passe)

Cependant, il est clair que peu de frais généraux ont été observés dans la plupart des cas pour le bâtiment d'un seul article, même avec de grands nombres.

Un inconvénient serait le fait que chacune des initiations de double accolade crée un nouveau fichier de classe qui ajoute un bloc de disque entier à la taille de notre application (ou environ 1k lorsqu'il est compressé). Une petite empreinte, mais si elle est utilisée dans de nombreux endroits, elle pourrait avoir un impact. Utilisez ceci 1000 fois et vous ajoutez potentiellement un MiB entier à votre application, ce qui peut être inquiétant dans un environnement embarqué.

Ma conclusion? Il peut être correct d'utiliser tant qu'il n'est pas abusé.

Laissez-moi savoir ce que vous pensez :)


Je seconde la réponse de Nat, sauf que j'utiliserais une boucle au lieu de créer et de lancer immédiatement la liste implicite de asList (elements):

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

Mis à part l'efficacité, je trouve rarement que je souhaite une création de collection déclarative en dehors des tests unitaires. Je crois que la syntaxe à double accolade est très lisible.

Une autre façon d'obtenir la construction déclarative des listes consiste à utiliser Arrays.asList(T ...) comme ceci:

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

La limitation de cette approche est bien sûr que vous ne pouvez pas contrôler le type spécifique de liste à générer.


Pour créer des ensembles, vous pouvez utiliser une méthode usine varargs au lieu de l'initialisation à double accolade:

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

La bibliothèque Google Collections a beaucoup de méthodes pratiques comme celle-ci, ainsi que de nombreuses autres fonctionnalités utiles.

Quant à l'obscurité de l'idiome, je le rencontre et je l'utilise en code de production tout le temps. Je serais plus préoccupé par les programmeurs qui sont confus par l'idiome étant autorisé à écrire le code de production.


Une propriété de cette approche qui n'a pas été signalée jusqu'à présent est que, parce que vous créez des classes internes, toute la classe contenant est capturée dans sa portée. Cela signifie que tant que votre Set est vivant, il conservera un pointeur sur l'instance contenant ( this$0 ) et gardera cela d'être garbage-collecté, ce qui pourrait être un problème.

Ceci, et le fait qu'une nouvelle classe soit créée en premier lieu même si un HashSet régulier fonctionnerait très bien (ou même mieux), ne me permet pas d'utiliser cette construction (même si j'ai vraiment envie du sucre syntaxique).

Deuxième question: Le nouveau HashSet doit être le "this" utilisé dans l'initialiseur d'instance ... quelqu'un peut-il faire la lumière sur le mécanisme? J'aurais naïvement attendu que "ceci" se réfère à l'objet initialisant "saveurs".

C'est juste comment les classes internes fonctionnent. Ils obtiennent leur propre this , mais ils ont aussi des pointeurs vers l'instance parent, de sorte que vous pouvez également appeler des méthodes sur l'objet conteneur. Dans le cas d'un conflit de noms, la classe interne (dans votre cas, HashSet) est prioritaire, mais vous pouvez préfixer "this" avec un nom de classe pour obtenir la méthode externe.

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

Pour être clair sur la sous-classe anonyme en cours de création, vous pouvez également définir des méthodes. Par exemple remplacer HashSet.add()

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

            @Override
            boolean add(String s){

            }

        };
    }

Voici le problème quand je suis trop emporté avec des classes internes anonymes:

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

Ce sont toutes les classes qui ont été générées lorsque je faisais une application simple, et qui utilisaient des quantités abondantes de classes internes anonymes - chaque classe sera compilée dans un fichier de class séparé.

L'initialisation "double accolade", comme déjà mentionné, est une classe interne anonyme avec un bloc d'initialisation d'instance, ce qui signifie qu'une nouvelle classe est créée pour chaque "initialisation", le tout dans le but de créer un seul objet.

Considérant que Java Virtual Machine aura besoin de lire toutes ces classes lors de leur utilisation, cela peut conduire à un certain temps dans le processus de vérification bytecode et autres. Sans parler de l'augmentation de l'espace disque nécessaire pour stocker tous ces fichiers de class .

Il semble qu'il y ait un peu de surcharge lors de l'utilisation de l'initialisation à double accolade, donc ce n'est probablement pas une bonne idée d'aller trop loin avec elle. Mais comme Eddie l'a noté dans les commentaires, il n'est pas possible d'être absolument sûr de l'impact.

Juste pour référence, l'initialisation à double accolade est la suivante:

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

Cela ressemble à une fonctionnalité "cachée" de Java, mais c'est juste une réécriture de:

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

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

Il s'agit donc essentiellement d'un bloc d'initialisation d'instance faisant partie d'une classe interne anonyme .

La proposition de Joshua Bloch sur Literal Collection pour Project Coin était la suivante:

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

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

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

Malheureusement, il n'a pas fait son chemin dans ni Java 7 ni 8 et a été mis de côté indéfiniment.

Expérience

Voici la simple expérience que j'ai testée - faites 1000 ArrayList avec les éléments "Hello" et "World!" ajouté à eux via la méthode add , en utilisant les deux méthodes:

Méthode 1: initialisation double accolade

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

Méthode 2: instancier une ArrayList et add

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

J'ai créé un programme simple pour écrire un fichier source Java pour effectuer 1000 initialisations en utilisant les deux méthodes:

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

Veuillez noter que le temps écoulé pour initialiser les 1000 ArrayList et les 1000 classes internes anonymes qui étendent ArrayList est vérifié en utilisant System.currentTimeMillis , donc le timer n'a pas une très haute résolution. Sur mon système Windows, la résolution est d'environ 15-16 millisecondes.

Les résultats pour 10 essais des deux tests étaient les suivants:

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

Comme on peut le voir, l'initialisation à double accolade a un temps d'exécution notable d'environ 190 ms.

Pendant ce temps, le temps d'exécution de l'initialisation ArrayList est passé à 0 ms. Bien sûr, la résolution de la minuterie doit être prise en compte, mais elle devrait être inférieure à 15 ms.

Donc, il semble y avoir une différence notable dans le temps d'exécution des deux méthodes. Il semble que les deux méthodes d'initialisation comportent un certain surcroît de temps.

Et oui, il y avait 1000 fichiers .class générés en compilant le programme de test d'initialisation double accolade Test1.


Chaque fois que quelqu'un utilise une initialisation double accolade, un chaton est tué.

Mis à part que la syntaxe est plutôt inhabituelle et pas vraiment idiomatique (le goût est discutable, bien sûr), vous créez inutilement deux problèmes importants dans votre application, dont j'ai récemment parlé plus en détail ici .

1. Vous créez beaucoup trop de classes anonymes

Chaque fois que vous utilisez l'initialisation double accolade, une nouvelle classe est créée. Par exemple cet exemple:

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

... produira ces classes:

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

C'est un peu de frais généraux pour votre classloader - pour rien! Bien sûr, cela ne prendra pas beaucoup de temps d'initialisation si vous le faites une fois. Mais si vous faites cela 20'000 fois dans votre application d'entreprise ... tout ce tas de mémoire juste pour un peu de "sucre de syntaxe"?

2. Vous créez potentiellement une fuite de mémoire!

Si vous prenez le code ci-dessus et renvoyez cette carte à partir d'une méthode, les appelants de cette méthode pourraient se retenir sans trop se préoccuper de ressources très lourdes qui ne peuvent pas être récupérées. Considérez l'exemple suivant:

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 retournée contiendra désormais une référence à l'instance englobante de ReallyHeavyObject . Vous ne voulez probablement pas risquer cela:

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

3. Vous pouvez prétendre que Java a des littéraux de carte

Pour répondre à votre question, les gens ont utilisé cette syntaxe pour prétendre que Java a quelque chose comme des littéraux de carte, semblables aux littéraux de tableau existants:

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

Certaines personnes peuvent trouver cela stimulant syntaxiquement.


fuite

J'ai décidé d'intervenir. L'impact sur les performances inclut: l'opération de disque + unzip (pour le pot), la vérification de classe, l'espace perm-gen (pour la JVM de Hotspot de Sun). Cependant, le pire de tout: c'est une fuite sujette. Vous ne pouvez pas simplement revenir.

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

Donc, si l'ensemble s'échappe vers une autre partie chargée par un chargeur de classe différent et qu'une référence est conservée, l'arbre entier de classes + classloader sera divulgué. Pour éviter cela, une copie à HashMap est nécessaire, new LinkedHashSet(new ArrayList(){{add("xxx);add("yyy");}}) . Pas si mignon. Je n'utilise pas l'idiome , moi-même, à la place c'est comme new LinkedHashSet(Arrays.asList("xxx","YYY"));





initialization