Dans les fonctions cachées de Java, la première réponse mentionne l' initialisation à double accolade , 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 quelle méthode [...] dans la portée contenante".
Question principale: est-ce aussi inefficace que cela puisse paraître? Son utilisation doit-elle être limitée à des initialisations ponctuelles? (Et bien sûr, exhibant!)
Deuxième question: le nouveau HashSet doit être le "ceci" 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 ont estimé que la syntaxe devrait être claire (bien que je recommanderais un commentaire occasionnel, surtout si votre code est transmis aux développeurs qui ne le connaissent peut-être pas).
Sur la question (1), le code généré doit s'exécuter rapidement. Les fichiers .class supplémentaires provoquent un encombrement des fichiers jar et ralentissent légèrement le démarrage du programme (merci à @coobird d'avoir mesuré 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 supplémentaires chargé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" faisant référence à 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 Java 7 Collection proposés. Les langages JVM plus récents comme Scala, JRuby et Groovy offrent également des notations concises pour la construction de listes et interagissent bien avec Java. Étant donné que DBI encombre le chemin de classe, ralentit un peu le chargement des classes et rend le code un peu plus obscur, je préfèrerais probablement l'éviter. Cependant, je prévois de lancer ceci sur un ami qui vient de recevoir son SCJP et aime les joutes de bonne nature sur la sémantique 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-modèle.
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 à coup sûr la voie à suivre. Si vous êtes bloqué avec une version antérieure, jetez un œil à ImmutableSet de Google Collections .
la source
flavors
à être unHashSet
, mais hélas c'est une sous-classe anonyme.Set<String> flavors = Set.of( "vanilla" , "strawberry" , "chocolate" , "butter pecan" ) ;
Réponses:
Voici le problème lorsque je m'emballe trop avec les classes internes anonymes:
Ce sont toutes des classes qui ont été générées lorsque je faisais une application simple, et utilisaient de grandes quantités de classes internes anonymes - chaque classe sera compilée dans un
class
fichier séparé .La "double initialisation d'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 généralement un seul objet.
Étant donné que la machine virtuelle Java devra lire toutes ces classes lors de leur utilisation, cela peut entraîner un certain temps dans le processus de vérification du bytecode et autres. Sans parler de l'augmentation de l'espace disque nécessaire pour stocker tous ces
class
fichiers.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 si bonne idée d'aller trop loin avec. 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:
Cela ressemble à une fonctionnalité "cachée" de Java, mais ce n'est qu'une réécriture de:
Il s'agit donc essentiellement d'un bloc d'initialisation d'instance qui fait partie d'une classe interne anonyme .
La proposition de Joshua Bloch Collection Literals pour Project Coin allait dans le sens de:
Malheureusement, il n'a fait son chemin ni dans Java 7 ni dans 8 et a été mis en suspens indéfiniment.
Expérience
Voici l'expérience simple que j'ai testée - faites 1000
ArrayList
s avec les éléments"Hello"
et"World!"
ajoutez-les via laadd
méthode, en utilisant les deux méthodes:Méthode 1: initialisation à double accolade
Méthode 2: instancier un
ArrayList
etadd
J'ai créé un programme simple pour écrire un fichier source Java pour effectuer 1000 initialisations en utilisant les deux méthodes:
Test 1:
Test 2:
Veuillez noter que le temps écoulé pour initialiser les 1000
ArrayList
s et les 1000 classes internes anonymes s'étendantArrayList
est vérifié à l'aide de laSystem.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:
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
temps d'exécution de l' initialisation s'est avéré être de 0 ms. Bien sûr, la résolution du temporisateur doit être prise en compte, mais elle est probablement inférieure à 15 ms.Ainsi, il semble y avoir une différence notable dans le temps d'exécution des deux méthodes. Il semble bien qu'il y ait effectivement des frais généraux dans les deux méthodes d'initialisation.
Et oui, il y avait 1000
.class
fichiers générés par la compilation duTest1
programme de test d'initialisation à double accolade.la source
Une propriété de cette approche qui n'a pas été souligné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 contenante (
this$0
) et l'empêchera d'être récupéré, ce qui pourrait être un problème.Cela, et le fait qu'une nouvelle classe soit créée en premier lieu même si un HashSet ordinaire fonctionnerait très bien (ou même mieux), me donne envie de ne pas utiliser cette construction (même si j'ai vraiment envie du sucre syntaxique).
C'est ainsi que fonctionnent les classes internes. Ils obtiennent le leur
this
, mais ils ont également des pointeurs vers l'instance parent, de sorte que vous pouvez également appeler des méthodes sur l'objet conteneur. En cas de conflit de dénomination, la classe interne (dans votre cas, HashSet) a priorité, mais vous pouvez préfixer "this" avec un nom de classe pour obtenir également la méthode externe.Pour être clair sur la sous-classe anonyme en cours de création, vous pouvez également y définir des méthodes. Par exemple, remplacer
HashSet.add()
la source
Chaque fois que quelqu'un utilise l'initialisation à double accolade, un chaton est tué.
Outre 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 je viens récemment de bloguer plus en détail ici .
1. Vous créez trop de classes anonymes
Chaque fois que vous utilisez l'initialisation à double accolade, une nouvelle classe est créée. Par exemple, cet exemple:
... produira ces classes:
C'est un peu de surcharge pour votre chargeur de classe - 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 ... toute cette mémoire 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 peuvent s'accrocher sans trop de ressources à des ressources très lourdes qui ne peuvent pas être récupérées. Prenons l'exemple suivant:
Le retour
Map
contiendra désormais une référence à l'instance englobante deReallyHeavyObject
. Vous ne voulez probablement pas risquer que: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 réelle, les gens ont utilisé cette syntaxe pour prétendre que Java a quelque chose comme des littéraux de carte, similaires aux littéraux de tableau existants:
Certaines personnes peuvent trouver cela stimulant sur le plan syntaxique.
la source
Prendre la classe de test suivante:
puis décompiler le fichier de classe, je vois:
Cela ne me semble pas terriblement inefficace. Si j'étais inquiet à propos des performances pour quelque chose comme ça, je le profilerais. Le code ci-dessus répond à votre question n ° 2: vous êtes dans un constructeur implicite (et un initialiseur d'instance) pour votre classe interne, "
this
" fait donc référence à cette classe interne.Oui, cette syntaxe est obscure, mais un commentaire peut clarifier l'utilisation d'une syntaxe obscure. Pour clarifier la syntaxe, la plupart des gens connaissent un bloc d'initialisation statique (JLS 8.7 Static Initializers):
Vous pouvez également utiliser une syntaxe similaire (sans le mot "
static
") pour l'utilisation du constructeur (initialiseurs d'instance JLS 8.6), bien que je n'ai jamais vu cela utilisé 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 constructeur par le compilateur. Dans cet esprit, démêlez le code à double accolade:Le bloc de code entre les accolades les plus internes est transformé en constructeur par le compilateur. Les accolades les plus externes délimitent la classe interne anonyme. Pour faire cela, la dernière étape de tout rendre non anonyme:
À des fins d'initialisation, je dirais qu'il n'y a pas de surcharge (ou si petite qu'elle puisse être négligée). Cependant, chaque utilisation de
flavors
n'ira pas contre,HashSet
mais contreMyHashSet
. Il y a probablement un petit (et probablement négligeable) frais généraux à cela. Mais encore une fois, avant de m'en inquiéter, je le profilais.Encore une fois, à votre question n ° 2, le code ci-dessus est l'équivalent logique et explicite de l'initialisation à double accolade, et il rend évident où "
this
" se réfère: à la classe interne qui s'étendHashSet
.Si vous avez des questions sur les détails des initialiseurs d'instance, consultez les détails dans la documentation JLS .
la source
super()
comme deuxième ligne du constructeur implicite, mais il doit venir en premier. (Je l'ai testé et il ne se compilera pas.)sujettes aux fuites
J'ai décidé d'intervenir. L'impact sur les performances comprend: le fonctionnement du disque + décompresser (pour le bocal), la vérification de classe, l'espace perm-gen (pour la JVM Hotspot de Sun). Mais le pire de tout: il est sujet aux fuites. Vous ne pouvez pas simplement revenir.
Donc, si l'ensemble s'échappe à toute autre partie chargée par un chargeur de classe différent et qu'une référence y est conservée, l'arborescence entière des classes + chargeur de classe sera divulguée. Pour éviter cela, une copie HashMap est nécessaire,
new LinkedHashSet(new ArrayList(){{add("xxx);add("yyy");}})
. Plus si mignon. Je n'utilise pas l'idiome, moi-même, c'est plutôtnew LinkedHashSet(Arrays.asList("xxx","YYY"));
la source
Le chargement de nombreuses 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.
impressions
la source
compareCollections
être combinées avec||
plutôt qu'avec&&
? L'utilisation&&
ne semble pas seulement sémantiquement incorrecte, elle contrecarre l'intention de mesurer les performances, car seule la première condition sera testée. De plus, un optimiseur intelligent peut reconnaître que les conditions ne changeront jamais pendant les itérations.Pour créer des ensembles, vous pouvez utiliser une méthode d'usine varargs au lieu de l'initialisation à double accolade:
La bibliothèque Google Collections propose de nombreuses méthodes pratiques comme celle-ci, ainsi que de nombreuses autres fonctionnalités utiles.
Quant à l'obscurité de l'idiome, je la rencontre et l'utilise tout le temps dans le code de production. Je serais plus préoccupé par les programmeurs qui sont confus par l'idiome autorisé à écrire du code de production.
la source
Mis à part l'efficacité, je me retrouve rarement à souhaiter la création d'une collection déclarative en dehors des tests unitaires. Je crois que la syntaxe à double accolade est très lisible.
Une autre façon de réaliser spécifiquement la construction déclarative des listes est d'utiliser
Arrays.asList(T ...)
comme ceci: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.
la source
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. Peu importe pour la JVM que vous ayez créé une sous-classe et y ayez ajouté un constructeur - c'est une chose normale et quotidienne à faire dans un langage orienté objet. Je peux penser à des cas assez artificiels où vous pourriez provoquer une inefficacité en faisant cela (par exemple, vous avez une méthode appelée à plusieurs reprises qui finit par prendre un mélange de différentes classes en raison de cette sous-classe, alors que la classe ordinaire transmise 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 importera sont très artificiels.
Je verrais le problème plus du point de vue de savoir si vous voulez "encombrer les choses" avec beaucoup de classes anonymes. À titre indicatif, envisagez d'utiliser l'idiome pas plus que vous n'utiliseriez, par exemple, des classes anonymes pour les gestionnaires d'événements.
En (2), vous êtes à l'intérieur du 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.
Quant à (3), cela dépend vraiment de qui gère votre code, je suppose. Si vous ne le savez pas à l'avance, alors une référence que je suggérerais d'utiliser est "voyez-vous cela dans le code source du JDK?" (dans ce cas, je ne me souviens pas avoir vu de nombreux initialiseurs anonymes, et certainement pas dans les cas où c'est le seul contenu de la classe anonyme). Dans la plupart des projets de taille modérée, je dirais que vous aurez vraiment besoin que vos programmeurs comprennent la source JDK à un moment ou à un autre, donc toute syntaxe ou idiome utilisé est un "jeu équitable". Au-delà de cela, je dirais, formez les gens à cette syntaxe si vous avez le contrôle de qui gère le code, sinon commentez ou évitez.
la source
L'initialisation à double accolade est un hack inutile qui peut introduire des fuites de mémoire et d'autres problèmes
Il n'y a aucune raison légitime d'utiliser cette "astuce". Guava fournit de belles collections immuables qui incluent à la fois des usines statiques et des constructeurs, vous permettant de remplir votre collection là où elle est déclarée dans une syntaxe propre, lisible et sûre .
L'exemple dans la question devient:
Non seulement c'est plus court et plus facile à lire, mais cela évite les nombreux problèmes avec le modèle à double entretoise décrit dans d' autres réponses . Bien sûr, il fonctionne de manière similaire à un modèle directement construit
HashMap
, mais il est dangereux et sujet aux erreurs, et il existe de meilleures options.Chaque fois que vous envisagez une initialisation à double croisillon, vous devez réexaminer vos API ou en introduire de nouvelles pour résoudre correctement le problème, plutôt que de profiter d'astuces syntaxiques.
Sujet aux erreurs marque désormais cet anti-modèle .
la source
static
contextes). Le lien sujet aux erreurs au bas de ma question en parle plus en détail.Je faisais des recherches sur cela 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 c'est ma conclusion
Laissez-moi savoir ce que vous pensez :)
la source
J'appuie 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 (éléments):
la source
HashSet
capacité suggérée - n'oubliez pas le facteur de charge).Bien que cette syntaxe puisse être pratique, elle ajoute également une grande partie de ces références $ 0 au fur et à mesure qu'elles sont imbriquées et il peut être difficile d'intégrer le débogage dans les initialiseurs à moins que des points d'arrêt ne soient définis sur chacun d'eux. Pour cette raison, je recommande uniquement d'utiliser ceci pour les setters banaux, en particulier les constantes, et les endroits où les sous-classes anonymes n'ont pas d'importance (comme aucune sérialisation impliquée).
la source
Mario Gleichman décrit comment utiliser les fonctions génériques Java 1.5 pour simuler les littéraux de la liste Scala, bien que vous vous retrouviez malheureusement avec des listes immuables .
Il définit cette classe:
et l'utilise ainsi:
Google Collections, qui fait maintenant partie de Guava, prend en charge une idée similaire pour la construction de listes. Dans cette interview , Jared Levy dit:
7/10/2014: Si seulement cela pouvait être aussi simple que celui de Python:
21/02/2020: En Java 11, vous pouvez maintenant dire:
la source
Cela appellera
add()
pour chaque membre. Si vous pouvez trouver un moyen plus efficace de placer des éléments dans un ensemble de hachage, utilisez-le. Notez que la classe interne générera probablement des ordures, si vous êtes sensible à cela.Il me semble que le contexte est l'objet renvoyé par
new
, qui est leHashSet
.Si vous avez besoin de demander ... Plus probablement: les gens qui viennent après vous le savent-ils ou non? Est-il facile à comprendre et à expliquer? Si vous pouvez répondre «oui» aux deux, n'hésitez pas à l'utiliser.
la source