Question Efficacité de Java "Initialisation Double Brace"?


Dans Les caractéristiques cachées de Java les réponses les plus élevées Initialisation double accolade, avec un très syntaxe 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 que c'est inefficace comme cela semble? 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: est-ce aussi l'idiome obscur utiliser 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 le mesurer). @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 proposés pour la collection Java 7. 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 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 coincé avec une version antérieure, jetez un oeil à Google Collections 'ImmutableSet.


686
2018-05-29 03:40


origine


Réponses:


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 quand je faisais une application simple, et qui utilisaient des quantités abondantes de classes internes anonymes - chaque classe sera compilée dans une classe séparée. class fichier.

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 verification par bytecode processus et tel. Sans parler de l'augmentation de l'espace disque nécessaire pour stocker tous les class des dossiers.

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

Donc, c'est fondamentalement un bloc d'initialisation d'instance cela fait partie d'un classe interne anonyme.


Joshua Bloch Proposition de Littéraux de Collection pour Pièce de projet était le long de:

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 en ni Java 7 ni 8 et a été mis en suspens indéfiniment.


Expérience

Voici l'expérience simple que j'ai testé - make 1000 ArrayLists avec les éléments "Hello" et "World!" ajouté à eux via le add méthode, 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 un 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 le 1000 ArrayLists et les 1000 classes intérieures anonymes étendant ArrayList est vérifié en utilisant le System.currentTimeMillis, donc la minuterie 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 ArrayList le temps d'exécution de l'initialisation est de 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 en avait 1000 .class fichiers générés en compilant le Test1 programme de test d'initialisation double accolade.


529
2018-05-29 03:59



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 gardez cela d'être ramassé des ordures, 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 donne pas envie d'utiliser cette construction (même si j'attends vraiment le 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){

            }

        };
    }

89
2018-05-29 05:37



Prendre la classe de test suivante:

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

puis décompiler le fichier de classe, je vois:

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

Cela ne me semble pas terriblement inefficace. Si j'étais inquiet de la performance pour quelque chose comme ça, je le profilerais. Et votre question # 2 est répondue par le code ci-dessus: Vous êtes dans un constructeur implicite (et initialiseur d'instance) pour votre classe interne, donc "this"se réfère à cette classe intérieure.

Oui, cette syntaxe est obscure, mais un commentaire peut clarifier l'utilisation de la syntaxe obscure. Pour clarifier la syntaxe, la plupart des gens connaissent un bloc d'initialisation statique (JLS 8.7 Static Initializers):

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

Vous pouvez également utiliser une syntaxe similaire (sans le mot "static") pour l'utilisation du constructeur (JLS 8.6 Instance Initializers), bien que je n'ai jamais vu cela utilisé dans le code de production, ce qui est beaucoup moins connu.

public class Sample2 {
    private final String someVar;

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

Si vous n'avez pas de constructeur par défaut, alors le bloc de code entre { et } est transformé en constructeur par le compilateur. Dans cet esprit, démêler le code double accolade:

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

Le bloc de code entre les accolades les plus internes est transformé en constructeur par le compilateur. Les accolades les plus extérieures délimitent la classe interne anonyme. Pour prendre cela la dernière étape de tout rendre non anonyme:

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

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

À des fins d'initialisation, je dirais qu'il n'y a aucun frais généraux (ou si petit qu'il peut être négligé). Cependant, chaque utilisation de flavors ira pas contre HashSet mais plutôt contre MyHashSet. Il y a probablement un petit surcoût (et peut-être négligeable) à cela. Mais encore une fois, avant que je m'inquiète à ce sujet, je le profilerais.

Encore une fois, à votre question # 2, le code ci-dessus est l'équivalent logique et explicite de l'initialisation à double accolade, et il le rend évident où "this"se réfère: à la classe interne qui s'étend HashSet.

Si vous avez des questions sur les détails des initialiseurs d'instance, consultez les détails dans le JLS Documentation.


34
2018-05-29 03:52



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, que j'ai récemment blogué sur 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;
    }
}

Le retourné Map va maintenant contenir une référence à l'instance englobante de ReallyHeavyObject. Vous ne voulez probablement pas risquer cela:

Memory Leak Right Here

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.


33
2017-12-17 08:56



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 plus. Je n'utilise pas l'idiome, moi-même, à la place c'est comme new LinkedHashSet(Arrays.asList("xxx","YYY"));


32
2018-01-31 13:46



Le chargement de plusieurs classes peut ajouter quelques millisecondes au début. Si le démarrage n'est pas si critique et que vous regardez l'efficacité des classes après le démarrage, il n'y a pas de différence.

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

imprime

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

18
2018-05-29 04:20



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.


16
2018-05-29 03:59



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 est d'utiliser Arrays.asList(T ...) ainsi:

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.


9
2018-05-29 05:26



Il n'y a généralement rien de particulièrement inefficace à ce sujet. Il n'est généralement pas important pour la JVM que vous ayez créé une sous-classe et y avez ajouté un constructeur - c'est une chose normale et quotidienne à faire dans un langage orienté objet. Je peux penser à des cas très artificiels où vous pourriez causer une inefficacité en faisant ceci (par exemple vous avez une méthode appelée à plusieurs reprises qui finit par prendre un mélange de classes différentes à cause de cette sous-classe, alors que la classe passée serait totalement prévisible- - dans ce dernier cas, le compilateur JIT pourrait faire des optimisations qui ne sont pas réalisables dans le premier). Mais vraiment, je pense que les cas où cela va importer sont très artificiels.

Je verrais le problème davantage du point de vue de savoir si vous voulez "encombrer les choses" avec beaucoup de cours anonymes. À titre indicatif, pensez à utiliser l'idiome pas plus que vous n'utiliseriez, disons, des classes anonymes pour les gestionnaires d'événements.

Dans (2), vous êtes dans le constructeur d'un objet, donc "ceci" fait référence à l'objet que vous construisez. Ce n'est pas différent de tout autre constructeur.

En ce qui concerne (3), cela dépend vraiment de qui maintient votre code, je suppose. Si vous ne le savez pas à l'avance, alors un point de référence que je suggère d'utiliser est "voyez-vous cela dans le code source du JDK?" (dans ce cas, je ne me souviens pas de voir beaucoup d'initialiseurs anonymes, et certainement pas dans les cas où c'est le seulement contenu de la classe anonyme). Dans la plupart des projets de taille moyenne, je dirais que vous allez vraiment avoir besoin de vos programmeurs pour comprendre la source JDK à un moment ou un autre, donc toute syntaxe ou idiome utilisé ici est "jeu équitable". Au-delà, je dirais, formez les gens sur cette syntaxe si vous avez le contrôle de qui maintient le code, sinon commentez ou évitez.


7
2017-12-24 12:17



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 :)


4
2018-05-29 04:50



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

3
2018-06-18 20:46