Qu'est-ce que la syntaxe d'initialisation Double Brace ( {{ ... }}
) en Java?
Qu'est-ce que la syntaxe d'initialisation Double Brace ( {{ ... }}
) en Java?
Réponses:
L'initialisation à double accolade crée une classe anonyme dérivée de la classe spécifiée (les accolades externes ) et fournit un bloc d'initialisation au sein de cette classe (les accolades internes ). par exemple
new ArrayList<Integer>() {{
add(1);
add(2);
}};
Notez qu'un effet de l'utilisation de cette initialisation à double accolade est que vous créez des classes internes anonymes. La classe créée a un this
pointeur implicite vers la classe externe environnante. Bien que ce ne soit normalement pas un problème, il peut provoquer des problèmes dans certaines circonstances, par exemple lors de la sérialisation ou de la collecte des ordures, et cela vaut la peine d'être conscient de cela.
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 .
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 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 ... tout ce tas de mémoire juste pour un peu de "sucre de syntaxe"?
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:
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 retour Map
contiendra désormais une référence à l'instance englobante de ReallyHeavyObject
. 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/
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:
String[] array = { "John", "Doe" };
Map map = new HashMap() {{ put("John", "Doe"); }};
Certaines personnes peuvent trouver cela stimulant sur le plan syntaxique.
{{...}}
et déclaré en tant que static
champ, il ne devrait pas y avoir de fuite de mémoire possible, une seule classe anonyme et aucune référence d'instance incluse, non?
Map.of()
à cet effet, donc ce sera une meilleure solution
ReallyHeavyObject
. De plus, les classes internes anonymes capturent toutes les variables locales utilisées dans le corps de la classe, donc si vous utilisez non seulement des constantes pour initialiser des collections ou des mappes avec ce modèle, les instances de classe interne les captureront toutes et continueront de les référencer même lorsqu'elles seront réellement supprimées de la collection ou la carte. Donc, dans ce cas, ces instances n'ont pas seulement besoin de deux fois plus de mémoire que nécessaire pour les références, mais ont une autre fuite de mémoire à cet égard.
Par exemple:
public class TestHashMap {
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<String,String>(){
{
put("1", "ONE");
}{
put("2", "TWO");
}{
put("3", "THREE");
}
};
Set<String> keySet = map.keySet();
for (String string : keySet) {
System.out.println(string+" ->"+map.get(string));
}
}
}
Comment ça fonctionne
La première accolade crée une nouvelle classe intérieure anonyme. Ces classes internes sont capables d'accéder au comportement de leur classe parent. Donc, dans notre cas, nous créons en fait une sous-classe de la classe HashSet, donc cette classe interne est capable d'utiliser la méthode put ().
Et le deuxième ensemble d'accolades n'est rien d'autre que des initialiseurs d'instance. Si vous rappelez les concepts de base de Java, vous pouvez facilement associer des blocs d'initialisation d'instance à des initialiseurs statiques en raison d'accolades similaires comme struct. La seule différence est que l'initialiseur statique est ajouté avec un mot clé statique et n'est exécuté qu'une seule fois; peu importe le nombre d'objets que vous créez.
Pour une application amusante d'initialisation à double accolade, voir ici Dwemthy's Array en Java .
Un extrait
private static class IndustrialRaverMonkey
extends Creature.Base {{
life = 46;
strength = 35;
charisma = 91;
weapon = 2;
}}
private static class DwarvenAngel
extends Creature.Base {{
life = 540;
strength = 6;
charisma = 144;
weapon = 50;
}}
Et maintenant, préparez-vous pour le baconBattleOfGrottoOfSausageSmells
et… gros morceaux!
Je pense qu'il est important de souligner qu'il n'y a rien de tel que «l'initialisation à double accolade» en Java . Le site Web d'Oracle n'a pas ce terme. Dans cet exemple, deux fonctionnalités sont utilisées ensemble: classe anonyme et bloc d'initialisation. Il semble que l'ancien bloc d'initialisation a été oublié par les développeurs et provoque une certaine confusion dans cette rubrique. Citation des documents Oracle :
Les blocs d'initialisation pour les variables d'instance ressemblent à des blocs d'initialisation statiques, mais sans le mot clé static:
{
// whatever code is needed for initialization goes here
}
1- Il n'y a pas de double accolade:
je voudrais souligner qu'il n'y a pas de telle chose que l'initialisation de double accolade. Il n'y a qu'un bloc d'initialisation traditionnel à une seule accolade. Le deuxième bloc d'accolades n'a rien à voir avec l'initialisation. Les réponses disent que ces deux accolades initialisent quelque chose, mais ce n'est pas comme ça.
2- Il ne s'agit pas seulement de classes anonymes mais de toutes les classes:
Presque toutes les réponses disent que c'est une chose utilisée lors de la création de classes internes anonymes. Je pense que les gens qui liront ces réponses auront l'impression que cela n'est utilisé que lors de la création de classes internes anonymes. Mais il est utilisé dans toutes les classes. La lecture de ces réponses ressemble à une toute nouvelle fonctionnalité spéciale dédiée aux classes anonymes et je pense que cela est trompeur.
3- Le but est simplement de placer les supports les uns après les autres, pas un nouveau concept: pour
aller plus loin, cette question parle de la situation où le deuxième support d'ouverture se trouve juste après la première ouverture du support. Lorsqu'il est utilisé dans une classe normale, il y a généralement du code entre deux accolades, mais c'est totalement la même chose. Il s'agit donc de placer des crochets. Je pense donc que nous ne devons pas dire que c'est quelque chose de nouveau passionnant, parce que c'est la chose que nous savons tous, mais juste écrite avec du code entre crochets. Nous ne devons pas créer de nouveau concept appelé "initialisation à double accolade".
4- La création de classes anonymes imbriquées n'a rien à voir avec deux accolades:
je ne suis pas d'accord avec l'argument selon lequel vous créez trop de classes anonymes. Vous ne les créez pas à cause d'un bloc d'initialisation, mais simplement parce que vous les créez. Ils seraient créés même si vous n'utilisiez pas l'initialisation à deux accolades, de sorte que ces problèmes se produiraient même sans initialisation ... L'initialisation n'est pas le facteur qui crée des objets initialisés.
De plus, nous ne devrions pas parler de problème créé en utilisant cette chose inexistante "initialisation à double accolade" ou même par l'initialisation normale d'une parenthèse, car les problèmes décrits n'existent que parce que la création d'une classe anonyme n'a donc rien à voir avec la question d'origine. Mais toutes les réponses donnent aux lecteurs l'impression que ce n'est pas la faute de créer des classes anonymes, mais cette chose mauvaise (inexistante) appelée "initialisation à double accolade".
Pour éviter tous les effets négatifs de l'initialisation à double accolade, tels que:
faire les choses suivantes:
Exemple:
public class MyClass {
public static class Builder {
public int first = -1 ;
public double second = Double.NaN;
public String third = null ;
public MyClass create() {
return new MyClass(first, second, third);
}
}
protected final int first ;
protected final double second;
protected final String third ;
protected MyClass(
int first ,
double second,
String third
) {
this.first = first ;
this.second= second;
this.third = third ;
}
public int first () { return first ; }
public double second() { return second; }
public String third () { return third ; }
}
Usage:
MyClass my = new MyClass.Builder(){{ first = 1; third = "3"; }}.create();
Avantages:
Désavantages:
Et, par conséquent, nous avons le modèle de générateur de java le plus simple jamais créé.
Voir tous les exemples sur github: java-sf-builder-simple-example
C'est - entre autres utilisations - un raccourci pour initialiser des collections. Apprendre encore plus ...
Vous pouvez mettre certaines instructions Java en boucle pour initialiser la collection:
List<Character> characters = new ArrayList<Character>() {
{
for (char c = 'A'; c <= 'E'; c++) add(c);
}
};
Random rnd = new Random();
List<Integer> integers = new ArrayList<Integer>() {
{
while (size() < 10) add(rnd.nextInt(1_000_000));
}
};
Comme l'a souligné @Lukas Eder, l' initialisation des doubles accolades doit être évitée.
Il crée une classe interne anonyme, et puisque toutes les classes internes gardent une référence à l'instance parent, il peut - et 99% le sera probablement - empêcher la récupération de place si ces objets de collection sont référencés par plus d'objets que le seul déclarant.
Java 9 a introduit des méthodes pratiques List.of
, Set.of
et Map.of
, qui doivent être utilisés à la place. Ils sont plus rapides et plus efficaces que l'initialiseur à double croisillon.
La première accolade crée une nouvelle classe anonyme et le deuxième ensemble d'accolades crée des initialiseurs d'instance comme le bloc statique.
Comme d'autres l'ont souligné, son utilisation n'est pas sûre.
Cependant, vous pouvez toujours utiliser cette alternative pour initialiser des collections.
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
List<String> list = List.of("A", "B", "C");
Il semblerait que ce soit le même que le mot clé with si populaire en flash et vbscript. C'est une méthode pour changer ce qui this
est et rien de plus.
this
est. La syntaxe crée simplement une classe anonyme (donc toute référence à this
ferait référence à l'objet de cette nouvelle classe anonyme), puis utilise un bloc d'initialisation {...}
afin d'initialiser l'instance nouvellement créée.