L'efficacité de Java “Double Croisillon d'Initialisation”?
Dans Les Fonctions cachées de Java la réponse sommet mentionne Double Croisillon D'Initialisation, avec un très séduisant syntaxe:
Set<String> flavors = new HashSet<String>() {{
add("vanilla");
add("strawberry");
add("chocolate");
add("butter pecan");
}};
Cet idiome crée un anonyme intérieur de la classe avec juste un exemple de l'initialiseur, ce qui "peut utiliser n'importe quel [...] les méthodes dans le champ d'application".
Question principale: Est-ce que inefficace comme il en a l'air? Son utilisation soit limitée à des initialisations? (Et bien montrer!)
Deuxième question: Le new HashSet doit être le "ce" utilisée dans l'instance de l'initialiseur ... quelqu'un peut jeter de la lumière sur le mécanisme?
Troisième question: Est-ce un langage trop obscur à utiliser dans la production de code?
Résumé: très, Très gentil réponses, merci à tous. Sur la question (3), les gens ont senti la syntaxe devrait être clair (même si je recommande un commentaire occasionnel, surtout si votre code est de transmettre aux développeurs qui peuvent ne pas être familiers avec elle).
Sur la question (1), le code généré doit s'exécuter rapidement. L'extra .les fichiers de classe font du fichier jar de l'encombrement, et de ralentir le démarrage du programme légèrement (merci à @coobird de mesure qui). @Thilo a souligné que la collecte des ordures peut être affectée, et le coût mémoire pour le chargement des classes peut être un facteur dans certains cas.
Question (2) s'est avéré pour être le plus intéressant pour moi. Si j'ai bien compris les réponses, ce qui se passe dans DBI, c'est que l'anonyme intérieure classe étend la classe de l'objet en cours de construction par le nouvel opérateur, et a donc un "cette" valeur de référence l'instance en cours de construction. Très soigné.
Dans l'ensemble, DBI, me paraît être quelque chose d'une curiosité intellectuelle. Coobird et de l'autre, vous pouvez obtenir le même effet avec des Tableaux.asList, varargs méthodes, Google Collections, et le projet de Java 7 de la Collection de littéraux. Nouveaux JVM langues comme Scala, JRuby, et Groovy offrent aussi concis que des notations de construction de liste, et fonctionnent bien avec Java. Étant donné que DBI encombre jusqu'au classpath, ralentit le chargement des classes un peu, et rend le code un peu plus obscure, je serais probablement de le fuir. Cependant, j'ai prévu pour ce printemps sur un ami qui venait tout juste de son SCJP et aime la bonne humeur des joutes sur la sémantique de Java! 😉 Merci à tous!
7/2017: Baeldung a un bon résumé de la double accolade de l'initialisation et le considère comme un anti-modèle.
12/2017: @Basile Bourque note que, dans le nouveau Java 9, vous pouvez dire:
Set<String> flavors = Set.of("vanilla", "strawberry", "chocolate", "butter pecan");
Qui est certainement le chemin à parcourir. Si vous êtes coincé avec une version antérieure, jetez un oeil à Google Collections ImmutableSet.
- L'odeur de code que je vois ici, c'est que le naïf lecteur pourrait s'attendre
flavors
être unHashSet
, mais hélas il est un anonyme sous-classe. - Si vous envisagez de courir à la place de la performance de chargement il n'y a pas de différence, voir ma réponse.
- J'aime que vous avez créé un résumé, je pense que c'est un exercice intéressant pour vous d'augmenter la compréhension et de la communauté.
- Il n'est pas obscur, à mon avis. Les lecteurs devraient savoir qu'un double ... o attendre, @ElazarLeibovich déjà dit que son commentaire. Double croisillon initialiseur lui-même n'existe pas en tant que construction du langage, c'est juste une combinaison d'un anonyme sous-classe et une instance d'initialiseur. La seule chose, c'est que les gens doivent être conscients de cela.
- L'efficacité n'est pas la question - DBI est dangereux et source d'erreurs.
- Belle chose que j'ai vu est le résumé; je ne vois dans aucune autre question.
- À mon avis, double croisillon d'initialisation est une mauvaise idée.
- Java 9 propose Immuable Ensemble Statique de l'Usine Méthodes qui peut remplacer l'utilisation de la DCI dans certaines situations:
Set<String> flavors = Set.of( "vanilla" , "strawberry" , "chocolate" , "butter pecan" ) ;
- Bourque: Oui, merci, je suis vraiment content qu'ils ont ajouté que! J'ai été en utilisant Google Collection ImmutableSet.de la(...) dans Java 8 et ne peut pas attendre jusqu'à ce que nous avons mise à niveau pour Java 9.
- Il n'y a toujours pas de moyen facile de les initialiser une mutable collection et pas de Java 9
List.of
n'est pas la même. - Dans Java 8, vous pouvez le faire
Stream.of("vanilla", "strawberry").collect(Collectors::toSet)
- Généralement un type particulier n'est pas importante en raison de la sous-typage.
- le point essentiel à comprendre le code de l'odeur de ici est habituellement. Aucun avantage évident, et hideuse subtil changement de comportement.
Vous devez vous connecter pour publier un commentaire.
Voici le problème quand je suis trop loin avec les classes internes anonymes:
Ce sont toutes les classes qui ont été générés, quand je faisais une application simple, et utilisé de grandes quantités d'anonymous inner classes: chaque classe seront compilées dans un
class
fichier.La "double croisillon d'initialisation", comme déjà mentionné, est un anonyme intérieur de la classe avec un exemple d'initialisation du bloc, ce qui signifie qu'une nouvelle classe est créée pour chaque "initialisation", tous dans le but de faire un objet unique.
Considérant que la Machine Virtuelle Java aurez besoin de lire toutes les classes lors de leur utilisation, qui peut conduire à un certain temps dans le la vérification de bytecode processus et ces. Pour ne pas mentionner l'augmentation de l'espace disque nécessaire pour stocker tous ces
class
fichiers.Il me semble que si il ya un peu de surcharge lors de l'utilisation de la double accolade de l'initialisation, donc c'est probablement pas une bonne idée d'aller trop trop loin avec elle. Mais comme Eddie a noté dans les commentaires, il n'est pas possible d'être absolument sûr de l'impact.
Juste pour la référence, double croisillon d'initialisation est la suivante:
Il ressemble à un "caché" de Java, mais c'est juste une réécriture de:
C'est en fait une exemple d'initialisation du bloc qui fait partie d'un anonyme intérieur de la classe.
Joshua Bloch de La collecte des Littéraux de proposition pour Projet De Pièce De Monnaie a été le long des lignes de:
Malheureusement, il ne pas faire son chemin dans ni de Java 7 ni 8 et a été abandonné indéfiniment.
Expérience
Voici la simple expérience, j'ai testé-faire 1000
ArrayList
s avec les éléments"Hello"
et"World!"
ajouté paradd
méthode, à l'aide de deux méthodes:Méthode 1: Double Croisillon Initialisation
Méthode 2: Instancier un
ArrayList
etadd
J'ai créé un programme simple d'écrire un fichier source de Java pour effectuer 1000 initialisation à l'aide de deux méthodes:
Test 1:
Test 2:
Veuillez noter que le temps nécessaire à l'initialisation de la 1000
ArrayList
s et le 1000 anonyme intérieure classes de l'extension deArrayList
est vérifiée à l'aide de laSystem.currentTimeMillis
, de sorte que 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 les 10 pistes des deux épreuves sont les suivantes:
Comme on peut le voir, le double croisillon initialisation d'un notable du temps d'exécution d'environ 190 ms.
Pendant ce temps, le
ArrayList
de l'initialisation de temps d'exécution est sorti à 0 ms. Bien sûr, la résolution du timer doit être pris en compte, mais il est susceptible d'être sous 15 ms.Donc, il semble y avoir une différence notable dans le temps d'exécution des deux méthodes. Il semble en effet qu'il existe une surcharge dans les deux méthodes d'initialisation.
Et oui, il y avait 1000
.class
les fichiers générés par la compilation de laTest1
double accolade de l'initialisation du programme de test.List<String> lst = new ArrayList<>()
), suivi par le contenu de l'initialiseur de bloc (par exemple,lst.add("Hello"); lst.add("World!")
), de sorte que le réel anonyme sous-classe est optimisé à l'écart?lst.getClass()==ArrayList.class
permettra d'évaluer àfalse
. Pas d'optimisation du compilateur est autorisé à modifier la sémantique du programme, même s'il est très probable que le programmeur n'a pas vraiment envie que sémantique.List<String> l1 = new ArrayList<>(List.of("Hello", "World"));
ou mêmeList<String> l1 = List.of("Hello", "World");
, si vous n'avez pas besoin d'unArrayList
, mais dans ce dernier cas, vous pourriez avoir utiliséList<String> l1 = Arrays.asList("Hello", "World");
depuis Java 5.Set<String> flavors = new HashSet<String>() {{ add("vanilla"); add("strawberry"); add("chocolate"); add("butter pecan"); }};
ne pas sauver quoi que ce soit par rapport àSet<String> flavors = new HashSet<>(); Collections.addAll(flavors, "vanilla", "strawberry", "chocolate", "butter pecan");
. Même une séquence de quatre manuelflavor.add
appels n'est pas beaucoup plus long que le “sucre syntaxique”. Ensuite, il y a la Java 9 alternativeSet<String> flavors = Set.of("vanilla", "strawberry", "chocolate", "butter pecan");
.Une propriété de cette approche qui n'a pas été signalé jusqu'à présent est que parce que vous créez les classes internes, l'ensemble de la classe conteneur est capturé dans son champ d'application. Cela signifie que tant que votre Jeu est en vie, il conserve un pointeur vers l'instance (
this$0
) et de garder que de le garbage collector, qui pourrait être un problème.Cela, et le fait qu'une nouvelle classe est créée, en premier lieu, même si régulièrement HashSet serait fonctionnent tout aussi bien (ou même mieux), fait que je ne veux pas utiliser cette construction (même si j'ai vraiment long pour le sucre syntaxique).
C'est juste la façon dont les classes internes de travail. Qu'ils obtiennent leur propre
this
, mais ils ont aussi des pointeurs vers le parent de l'instance, de sorte que vous pouvez appeler des méthodes sur l'objet contenant ainsi. Dans le cas d'un conflit de noms, à l'intérieur de la classe (dans votre cas, HashSet) prend le pas, mais vous pouvez le préfixe "ce" avec un nom de classe pour obtenir la méthode extérieure ainsi.Être clair sur la anonyme sous-classe en cours de création, vous pouvez définir des méthodes de là aussi bien. Par exemple remplacer
HashSet.add()
Chaque fois que quelqu'un utilise la double accolade de l'initialisation, un chaton se fait tuer.
En dehors de la syntaxe plutôt inhabituel et pas vraiment idiomatiques (le goût est discutable, bien sûr), vous êtes inutilement la création de deux problèmes importants dans votre application, qui j'ai tout récemment blogué au sujet plus en détail ici.
1. Vous êtes en train de créer trop de anonyme classes
Chaque fois que vous utilisez une double accolade de l'initialisation d'une nouvelle classe est faite. E. g. cet exemple:
... va produire ces classes:
C'est tout à fait un peu de surcharge de votre chargeur de classe - pour rien! Bien sûr, il ne prendra pas beaucoup de temps d'initialisation si vous le faites une fois. Mais si vous faites cela 20'000 de fois tout au long de votre application d'entreprise... tout ce que la mémoire du tas juste pour un peu de syntaxe "sucre"?
2. Vous êtes potentiellement la création d'une fuite de mémoire!
Si vous prenez le code ci-dessus et retourner cette carte à partir d'une méthode, les appelants de la méthode, peut-être sans s'en douter, se tenant à de très lourdes ressources qui ne peuvent être nettoyés. Considérons l'exemple suivant:
Le retour de l'
Map
contient maintenant une référence à la enfermant instance deReallyHeavyObject
. Vous ne voulez probablement pas à risque: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 la carte littéraux
Pour répondre à votre question, les gens ont été en utilisant cette syntaxe pour faire semblant que Java a quelque chose comme une carte de littéraux, semblable à la matrice de littéraux:
Certaines personnes peuvent trouver cela un point de vue syntaxique stimulant.
Prenant la suite de la classe de test:
et puis décompiler le fichier de classe, je vois:
Cela n'a pas l'air terriblement inefficace pour moi. Si j'étais inquiet au sujet de la performance pour quelque chose comme ça, je ferais profil. Et votre question n ° 2 est répondu par le code ci-dessus: Vous êtes à l'intérieur d'un constructeur implicite (l'instance et de l'initialiseur) pour l'intérieur de votre classe, de sorte que "
this
" désigne le présent intérieure.Oui, cette syntaxe est obscure, mais un commentaire peut clarifier obscur de la syntaxe d'utilisation. Pour clarifier la syntaxe, la plupart des gens sont familiers avec un initialiseur statique bloc (JLS 8.7 Initialiseurs Statiques):
Vous pouvez également utiliser une syntaxe similaire (sans le mot "
static
") pour le constructeur d'utilisation (JLS 8.6 Exemple les Initialiseurs), bien que je n'ai jamais vu cela dans le code de production. C'est beaucoup moins connu.Si vous n'avez pas de constructeur par défaut, le bloc de code entre
{
et}
est transformé en un constructeur par le compilateur. Avec cela à l'esprit, à démêler la double croisillon code:Le bloc de code entre à l'intérieur de la plupart des accolades est transformé en un constructeur par le compilateur. L'extérieur-la plupart des accolades délimiter l'anonyme intérieur de la classe. Pour profiter de cette la dernière étape de faire tout ce non-anonyme:
Des fins d'initialisation, je dirais qu'il y a pas de frais généraux que ce soit (ou si petite qu'elle peut être négligée). Cependant, chaque utilisation de
flavors
ira pas à l'encontre deHashSet
mais plutôt contreMyHashSet
. Il y a probablement une petite (et très probablement négligeable de charge) pour cela. Mais encore une fois, avant que je inquiet à ce sujet, je voudrais profil.Encore une fois, à votre question #2, le code ci-dessus est la logique et explicite équivalent de la double accolade de l'initialisation, et il le fait évident où "
this
" fait référence: À l'intérieur de la classe qui étend la classeHashSet
.Si vous avez des questions sur les détails de l'instance initialiseurs, découvrez les détails dans la JLS de la documentation.
super()
que la deuxième ligne de l'implicite du constructeur, mais il doit venir en premier. (Je l'ai testé, et ça ne compile pas.)Set<String> flavors = new HashSet<String>(); Collections.addAll(flavors, "vanilla", "strawberry", "chocolate", "butter pecan");
est encore plus court, plus lisible, fonctionne aussi avec les types de collection appuie pas le sous-classement, et n'a rien de la double accolade inconvénients.fuite sujettes
J'ai décidé de carillon. L'impact de la performance comprend: opération de disque + décompresser (jar), la classe de vérification, perm-gen de l'espace (pour le Soleil de la JVM Hotspot).
Cependant, le pire de tous: c'est une fuite sur le ventre. Vous ne pouvez pas il suffit de le retourner.
Donc, si l'ensemble échappe à toute autre partie chargée par un chargeur de classes différentes et une référence y est conservé, l'ensemble de l'arborescence de classes+chargeur de classe sera coulé. Pour éviter cela, une copie de HashMap est nécessaire,
new LinkedHashSet(new ArrayList(){{add("xxx);add("yyy");}})
. Pas très joli, pas plus.Je n'utilise pas le langage, moi-même, c'est plutôt comme
new LinkedHashSet(Arrays.asList("xxx","YYY"));
Chargement d'un grand nombre de classes peut ajouter quelques millisecondes pour le départ. Si le démarrage n'est pas critique et que vous regardez l'efficacité des classes après le démarrage il n'y a pas de différence.
imprime
compareCollections
combinées avec||
plutôt que&&
? À l'aide de&&
semble non seulement du point de vue sémantique de mal, il s'oppose à l'intention de la mesure de la performance, seule la première condition sera testé à tous. En outre, une smart optimiseur peut reconnaître que les conditions ne changeront jamais, au cours des itérations.Pour créer des jeux, vous pouvez utiliser un varargs usine méthode au lieu de double-brace initialisation:
Google Collections de la bibliothèque a beaucoup de méthodes pratiques de ce genre, ainsi que des tas d'autres fonctions utiles.
Comme pour l'idiome de l'obscurité, je rencontre et l'utiliser dans la production de code de tous les temps. Je serais plus préoccupé par les programmeurs qui se confondre par l'idiome d'être autorisé à écrire du code de production.
L'efficacité de côté, je trouve rarement moi-même qui souhaitent déclarative création de la collection de l'extérieur de l'unité des tests. Je ne crois pas que la double croisillon syntaxe est très lisible.
Une autre façon d'obtenir le déclaratif de la construction de listes plus précisément, c'est d'utiliser
Arrays.asList(T ...)
comme suit:La limitation de cette approche est bien sûr que vous ne pouvez pas contrôler le type spécifique de la liste à être généré.
new ArrayList<String>(Arrays.asList("vanilla", "strawberry", "chocolate"))
pour contourner ce problème.Il n'y a généralement rien de particulièrement inefficace à ce sujet. Il n'est généralement pas la question de la JVM que vous avez fait une sous-classe et de l'ajout d'un constructeur à cela, c'est normal, tous les jours de chose à faire dans un langage orienté-objet. Je pense tout à fait artificiel cas où vous pourrait entraîner une inefficacité en faisant cela (par exemple, vous avez a à plusieurs reprises appelé méthode qui prend un mélange de différentes classes en raison de la présente sous-classe, alors que l'ordinaire de la classe passée en 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 le cas où il va la matière sont très artificiel.
J'ai vu l'émission de plus, du point de vue de savoir si vous voulez "le désordre des choses" avec beaucoup de classes anonymes. À titre indicatif, pensez à utiliser l'idiome pas plus que vous ne l'utiliser, disons, anonyme classes pour les gestionnaires d'événements.
Dans (2), vous êtes à l'intérieur, le constructeur d'un objet, de sorte que "cela" se réfère à l'objet que vous êtes la construction. Ce n'est pas différent de n'importe quel autre constructeur.
Que pour (3), qui dépend vraiment de qui est le maintien de votre code, je suppose. Si vous ne savez pas à l'avance, puis une référence que je conseille est "avez-vous le voir dans le code source du JDK?" (dans ce cas, je ne me souviens pas avoir vu de nombreux anonymes initialisers, et certainement pas dans les cas où c'est le seulement contenu de la classe anonyme). Dans la plupart de taille modérée projets, je dirais que vous allez vraiment avoir besoin de vos programmeurs à comprendre le JDK source à un moment ou un autre, de sorte que toute la syntaxe ou l'idiome utilisé, il est "fair game". Au-delà, je dirais, de former les gens sur cette syntaxe si vous avez le contrôle de l'oms pour maintenir le code, d'autre commentaire ou à éviter.
Double accolade de l'initialisation est inutile de faire des hack qui peut présenter des fuites de mémoire et d'autres questions
Il n'y a pas de raison légitime d'utiliser ce "truc". Goyave donne une belle immuable collections qui incluent à la fois statique des usines et des constructeurs, vous permettant de remplir votre collection, où il est déclaré dans un endroit propre, lisible, et sûr syntaxe.
L'exemple dans la question devient:
Non seulement c'est plus court et plus facile à lire, mais il évite de nombreux problèmes avec la double-arc-boutée modèle décrit dans d'autres réponses. Bien sûr, il s'agit de la même manière à une directement construit
HashMap
, mais c'est dangereux et source d'erreurs, et il ya de meilleures options.Tout moment vous vous trouvez en considérant double-arc-boutée d'initialisation, vous devriez revoir vos Api ou introduire de nouveaux de traiter correctement la question, plutôt que de prendre avantage de la syntaxiques astuces.
Le Risque D'Erreur De maintenant les drapeaux de cet anti-modèle.
Je faisais des recherches sur ce et a décidé de faire un plus dans le test de profondeur que celle fournie par la réponse valable.
Voici le code: https://gist.github.com/4368924
et c'est ma conclusion
Laissez-moi savoir ce que vous en pensez 🙂
J'deuxième Nat réponse, sauf que je voudrais utiliser une boucle au lieu de créer et immédiatement jeter l'implicite Liste de asList(éléments):
HashSet
une suggestion de la capacité de mémoriser le facteur de charge).Bien que cette syntaxe peut être pratique, elle ajoute également beaucoup de ce$0 références qu'elles sont imbriquées et il peut être difficile de l'étape de débogage dans les initialiseurs de moins que les points d'arrêt sont réglés sur chacun d'eux. Pour cette raison, je ne recommande l'utilisation de ce banal pour les setters, en particulier de définir des constantes, et les lieux où l'anonyme sous-classes n'a pas d'importance (pas comme la sérialisation impliqués).
Mario Gleichman décrit comment utiliser Java 1.5 fonctions génériques pour simuler Scala Liste des littéraux, mais malheureusement vous vous retrouvez avec immuable Listes.
Il définit cette classe:
et l'utilise comme ceci:
Google Collections, qui fait maintenant partie de Goyave prend en charge une idée similaire pour la construction de liste. Dans cette interview, Jared Levy dit:
7/10/2014: Si seulement ça pouvait être aussi simple que de Python:
Cela appel
add()
pour chaque membre. Si vous pouvez trouver un moyen plus efficace de mettre les éléments dans une table de hachage ensemble, puis l'utiliser. Notez que l'intérieur de la classe sera susceptible de générer des ordures, si vous êtes sensibles à ce sujet.Il me semble que si le contexte est l'objet renvoyé par
new
, qui est leHashSet
.Si vous devez demander à... Plus de chances: les gens qui viennent après vous savez cela ou non? Est-il facile à comprendre et à expliquer? Si vous pouvez répondre "oui" à la fois, n'hésitez pas à l'utiliser.