Qu'est-ce que l'initialisation Double Brace en Java?

307

Qu'est-ce que la syntaxe d'initialisation Double Brace ( {{ ... }}) en Java?

sgokhales
la source
2
Voir aussi stackoverflow.com/q/924285/45935
Jim Ferrans
10
L'initialisation à double accolade est une fonctionnalité très dangereuse et doit être utilisée judicieusement. Il peut rompre un contrat égal et introduire des fuites de mémoire délicates. Cet article décrit les détails.
Andrii Polunin

Réponses:

303

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 thispointeur 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.

Brian Agnew
la source
11
Merci d'avoir clarifié la signification des accolades intérieure et extérieure. Je me suis demandé pourquoi il y avait soudainement deux accolades autorisées avec une signification particulière, alors qu'il s'agit en fait de constructions Java normales qui n'apparaissent que comme une nouvelle astuce magique. Des choses comme ça me font remettre en question la syntaxe Java. Si vous n'êtes pas déjà un expert, il peut être très difficile de lire et d'écrire.
jackthehipster
4
La "syntaxe magique" comme celle-ci existe dans de nombreux langages, par exemple presque tous les langages de type C supportent la syntaxe "va à 0" de "x -> 0" dans les boucles qui est juste "x--> 0" avec bizarre placement de l'espace.
Joachim Sauer
17
Nous pouvons simplement conclure que "l'initialisation à double accolade" n'existe pas en soi, c'est juste une combinaison de la création d'une classe anonyme et d'un bloc d'initialisation , qui, une fois combinés, ressemble à une construction syntaxique, mais en réalité, ce n'est pas le cas ' t.
MC Emperor
Je vous remercie! Gson renvoie null lorsque nous sérialisons quelque chose avec une initialisation à double accolade en raison de l'utilisation anonyme de la classe interne.
Pradeep AJ- Msft MVP
295

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 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 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"?

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:

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

Fuite de mémoire ici

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:

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

Certaines personnes peuvent trouver cela stimulant sur le plan syntaxique.

Lukas Eder
la source
11
"Vous créez trop de classes anonymes" - en regardant comment (disons) Scala crée des classes anonymes, je ne suis pas trop sûr que ce soit un problème majeur
Brian Agnew
2
Ne reste-t-il pas un moyen valide et agréable de déclarer des cartes statiques? Si un HashMap est initialisé avec {{...}}et déclaré en tant que staticchamp, 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?
lorenzo-s
8
@ lorenzo-s: Oui, 2) et 3) ne s'appliquent pas alors, seulement 1). Heureusement, avec Java 9, il y a enfin Map.of()à cet effet, donc ce sera une meilleure solution
Lukas Eder
3
Il convient de noter que les cartes internes ont également des références aux cartes externes et donc indirectement à 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.
Holger
41
  • La première accolade crée une nouvelle classe intérieure anonyme.
  • Le deuxième ensemble d'accolades crée des initialiseurs d'instance comme un bloc statique dans Class.

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.

plus

Premraj
la source
24

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!

akuhn
la source
16

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
}
Alex T
la source
11

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".

ctomek
la source
9

Pour éviter tous les effets négatifs de l'initialisation à double accolade, tels que:

  1. Compatibilité "égale" cassée.
  2. Aucune vérification effectuée lors de l'utilisation d'affectations directes.
  3. Fuites de mémoire possibles.

faire les choses suivantes:

  1. Créez une classe "Builder" séparée spécialement pour l'initialisation à double accolade.
  2. Déclarez les champs avec des valeurs par défaut.
  3. Mettez la méthode de création d'objets dans cette classe.

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:

  1. Simplement à utiliser.
  2. Ne rompt pas la compatibilité «égal».
  3. Vous pouvez effectuer des vérifications dans la méthode de création.
  4. Aucune fuite de mémoire.

Désavantages:

  • Aucun.

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

Boris Podchezertsev
la source
4

C'est - entre autres utilisations - un raccourci pour initialiser des collections. Apprendre encore plus ...

miku
la source
2
Eh bien, c'est une application pour cela, mais en aucun cas la seule.
skaffman
4

Tu veux dire quelque chose comme ca?

List<String> blah = new ArrayList<String>(){{add("asdfa");add("bbb");}};

c'est une initialisation de liste de tableaux au moment de la création (hack)

dhblah
la source
4

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

Mais ce cas affecte les performances, consultez cette discussion

Anton Dozortsev
la source
4

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.ofet Map.of, qui doivent être utilisés à la place. Ils sont plus rapides et plus efficaces que l'initialiseur à double croisillon.

Mikhail Kholodkov
la source
0

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.

  • Java 8
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
  • Java 9
List<String> list = List.of("A", "B", "C");
Ankit Sharma
la source
-1

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 thisest et rien de plus.

Chuck Vose
la source
Pas vraiment. Ce serait comme dire que la création d'une nouvelle classe est une méthode pour changer ce qui thisest. La syntaxe crée simplement une classe anonyme (donc toute référence à thisferait référence à l'objet de cette nouvelle classe anonyme), puis utilise un bloc d'initialisation {...}afin d'initialiser l'instance nouvellement créée.
grinch