O que é a inicialização do Double Brace em Java?

307

O que é a sintaxe de inicialização do Double Brace ( {{ ... }}) em Java?

sgokhales
fonte
2
Veja também stackoverflow.com/q/924285/45935
Jim Ferrans
10
A inicialização do Double Brace é um recurso muito perigoso e deve ser usado criteriosamente. Pode quebrar o contrato igual e introduzir vazamentos de memória complicados. Este artigo descreve os detalhes.
Andrii Polunin

Respostas:

303

A inicialização entre chaves cria uma classe anônima derivada da classe especificada ( chaves externas ) e fornece um bloco inicializador nessa classe ( chaves internas ). por exemplo

new ArrayList<Integer>() {{
   add(1);
   add(2);
}};

Observe que um efeito do uso dessa inicialização entre chaves é que você está criando classes internas anônimas. A classe criada possui um thisponteiro implícito para a classe externa circundante. Embora normalmente não seja um problema, ele pode causar pesar em algumas circunstâncias, por exemplo, quando serializar ou coletar lixo, e vale a pena estar ciente disso.

Brian Agnew
fonte
11
Obrigado por esclarecer o significado dos aparelhos interno e externo. Eu me perguntei por que de repente duas chaves são permitidas com um significado especial, quando na verdade são construções java normais que aparecem apenas como um novo truque mágico. Coisas assim me fazem questionar a sintaxe Java. Se você ainda não é um especialista, pode ser muito complicado ler e escrever.
jackthehipster
4
"Sintaxe mágica" como essa existe em muitos idiomas, por exemplo, quase todos os idiomas do tipo C suportam a sintaxe "vai para 0" de "x -> 0" para loops que são apenas "x--> 0" com estranhos colocação de espaço.
Joachim Sauer
17
Podemos concluir que a "inicialização entre chaves" não existe por si só, é apenas uma combinação de criação de uma classe anônima e um bloco inicializador que, uma vez combinados, parece uma construção sintática, mas, na realidade, não é ' t.
MC Emperor
Obrigado! O Gson retorna nulo quando serializamos algo com inicialização entre chaves por causa do uso anônimo da classe interna.
Pradeep AJ Msft MVP
295

Toda vez que alguém usa a inicialização com chaves duplas, um gatinho é morto.

Além da sintaxe ser incomum e não realmente idiomática (o gosto é discutível, é claro), você está desnecessariamente criando dois problemas significativos em seu aplicativo, sobre os quais recentemente escrevi em detalhes aqui .

1. Você está criando muitas classes anônimas

Cada vez que você usa a inicialização entre chaves, uma nova classe é feita. Por exemplo, este exemplo:

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

... produzirá estas classes:

Test$1$1$1.class
Test$1$1$2.class
Test$1$1.class
Test$1.class
Test.class

Isso representa um pouco de sobrecarga para o seu carregador de classe - por nada! É claro que não levará muito tempo de inicialização se você fizer isso uma vez. Mas se você fizer isso 20.000 vezes em todo o aplicativo corporativo ... toda essa memória acumulada apenas por um pouco de "açúcar de sintaxe"?

2. Você está potencialmente criando um vazamento de memória!

Se você pegar o código acima e retornar esse mapa a partir de um método, os chamadores desse método poderão estar inconscientemente segurando recursos muito pesados ​​que não podem ser coletados com lixo. Considere o seguinte exemplo:

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

O retornado Mapagora conterá uma referência à instância anexa de ReallyHeavyObject. Você provavelmente não quer arriscar que:

Vazamento de memória aqui

Imagem de http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/

3. Você pode fingir que Java possui literais de mapa

Para responder à sua pergunta real, as pessoas têm usado essa sintaxe para fingir que Java tem algo como literais de mapa, semelhante aos literais de matriz existentes:

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

Algumas pessoas podem achar isso sintaticamente estimulante.

Lukas Eder
fonte
11
"Você está criando forma muitas classes anônimas" - olhando como (por exemplo) Scala cria classes anônimas, eu não estou muito certo de que este é um grande problema
Brian Agnew
2
Não permanece uma maneira válida e agradável de declarar mapas estáticos? Se um HashMap é inicializado {{...}}e declarado como um staticcampo, não deve haver nenhum vazamento de memória possível, apenas uma classe anônima e nenhuma referência de instância fechada, certo?
lorenzo-s
8
@ Lorenzo-s: Sim, 2) e 3) não se aplicam, apenas 1). Felizmente, com Java 9, há, finalmente, Map.of()para o efeito, de modo que vai ser uma solução melhor
Lukas Eder
3
Pode ser interessante notar que os mapas internos também têm referências aos mapas externos e, portanto, indiretamente a ReallyHeavyObject. Além disso, as classes internas anônimas capturam todas as variáveis ​​locais usadas no corpo da classe; portanto, se você usar não apenas constantes para inicializar coleções ou mapas com esse padrão, as instâncias da classe interna capturarão todas elas e continuarão a referenciá-las, mesmo quando realmente removidas de a coleção ou mapa. Nesse caso, essas instâncias não precisam apenas do dobro da memória necessária para as referências, mas têm outro vazamento de memória a esse respeito.
Holger
41
  • A primeira chave cria uma nova classe interna anônima.
  • O segundo conjunto de chaves cria inicializadores de instância como bloco estático na classe.

Por exemplo:

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

}

Como funciona

A primeira chave cria uma nova classe interna anônima. Essas classes internas são capazes de acessar o comportamento de sua classe pai. Portanto, no nosso caso, estamos realmente criando uma subclasse da classe HashSet, portanto essa classe interna é capaz de usar o método put ().

E o segundo conjunto de chaves não passa de inicializadores de instância. Se você lembrar os conceitos principais do java, poderá associar facilmente os blocos do inicializador da instância aos inicializadores estáticos devido a chaves semelhantes a struct. A única diferença é que o inicializador estático é adicionado à palavra-chave estática e é executado apenas uma vez; não importa quantos objetos você crie.

Mais

Premraj
fonte
24

Para uma aplicação divertida de inicialização entre chaves, veja aqui Dwemthy's Array em Java .

Um trecho

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

E agora, esteja preparado para o BattleOfGrottoOfSausageSmellse… bacon robusto!

akuhn
fonte
16

Eu acho importante enfatizar que não existe algo como "inicialização de chave dupla" em Java . O site da Oracle não possui esse termo. Neste exemplo, existem dois recursos usados ​​juntos: classe anônima e bloco inicializador. Parece que o antigo bloco inicializador foi esquecido pelos desenvolvedores e causa alguma confusão neste tópico. Citação de documentos da Oracle :

Os blocos do inicializador, por exemplo, variáveis ​​parecem exatamente com os blocos estáticos do inicializador, mas sem a palavra-chave estática:

{
    // whatever code is needed for initialization goes here
}
Alex T
fonte
11

1- Não existe colchetes duplos:
gostaria de salientar que não existe inicialização de colchetes duplos. Existe apenas um bloco de inicialização tradicional normal entre chaves. O segundo bloco de chaves não tem nada a ver com a inicialização. As respostas dizem que esses dois aparelhos inicializam algo, mas não é assim.

2- Não se trata apenas de classes anônimas, mas de todas as classes:
quase todas as respostas falam que isso é algo usado na criação de classes internas anônimas. Eu acho que as pessoas que lêem essas respostas terão a impressão de que isso é usado apenas ao criar classes internas anônimas. Mas é usado em todas as classes. Ler essas respostas parece um novo recurso especial dedicado a classes anônimas e acho que isso é enganoso.

3- O objetivo é colocar colchetes um após o outro, não um novo conceito:
Indo além, essa pergunta fala sobre a situação em que o segundo colchete de abertura é logo após o primeiro colchete de abertura. Quando usado na classe normal, geralmente existe algum código entre duas chaves, mas é totalmente a mesma coisa. Portanto, é uma questão de colocar colchetes. Então, acho que não devemos dizer que isso é algo novo e empolgante, porque é o que todos sabemos, mas que foi escrito com algum código entre colchetes. Não devemos criar um novo conceito chamado "inicialização entre chaves".

4- A criação de classes anônimas aninhadas não tem nada a ver com duas chaves:
não concordo com o argumento de que você cria muitas classes anônimas. Você não os está criando porque é um bloco de inicialização, mas apenas porque você os cria. Eles seriam criados mesmo se você não usasse a inicialização de dois chaves, para que esses problemas ocorressem mesmo sem a inicialização ... A inicialização não é o fator que cria objetos inicializados.

Além disso, não devemos falar sobre o problema criado usando essa coisa inexistente "inicialização entre chaves" ou mesmo a inicialização normal entre chaves, porque os problemas descritos existem apenas por causa da criação de classe anônima, por isso não tem nada a ver com a pergunta original. Mas todas as respostas dão aos leitores a impressão de que não é culpa de criar classes anônimas, mas essa coisa maligna (inexistente) chamada "inicialização entre chaves".

ctomek
fonte
9

Para evitar todos os efeitos negativos da inicialização entre chaves, como:

  1. Compatibilidade "igual" quebrada.
  2. Não são realizadas verificações ao usar atribuições diretas.
  3. Possíveis vazamentos de memória.

faça as próximas coisas:

  1. Crie uma classe "Builder" separada, especialmente para inicialização com chaves duplas.
  2. Declarar campos com valores padrão.
  3. Coloque o método de criação de objeto nessa classe.

Exemplo:

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

Uso:

MyClass my = new MyClass.Builder(){{ first = 1; third = "3"; }}.create();

Vantagens:

  1. Simplesmente de usar.
  2. Não quebra a compatibilidade "igual".
  3. Você pode executar verificações no método de criação.
  4. Sem vazamentos de memória.

Desvantagens:

  • Nenhum.

E, como resultado, temos o padrão mais simples de construtor de java de todos os tempos.

Veja todos os exemplos no github: java-sf-builder-simple-example

Boris Podchezertsev
fonte
4

É - entre outros usos - um atalho para inicializar coleções. Saber mais ...

miku
fonte
2
Bem, essa é uma aplicação, mas de nenhuma maneira é a única.
22410 skaffman
4

você quer dizer algo assim?

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

é uma inicialização da lista de arrays no momento da criação (hack)

dhblah
fonte
4

Você pode colocar algumas instruções Java como loop para inicializar a coleção:

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

Mas este caso afeta o desempenho, verifique esta discussão

Anton Dozortsev
fonte
4

Como apontado por @Lukas Eder, entre chaves, a inicialização de coleções deve ser evitada.

Ele cria uma classe interna anônima e, como todas as classes internas mantêm uma referência à instância pai, ela pode - e 99% provavelmente evitará - a coleta de lixo se esses objetos de coleção forem referenciados por mais objetos do que apenas o declarante.

Java 9 introduziu métodos de conveniência List.of, Set.ofe Map.of, que devem ser usados em seu lugar. Eles são mais rápidos e eficientes do que o inicializador de cinta dupla.

Mikhail Kholodkov
fonte
0

O primeiro colchete cria uma nova classe anônima e o segundo conjunto de colchetes cria inicializadores de instância como o bloco estático.

Como outros já apontaram, não é seguro usá-lo.

No entanto, você sempre pode usar esta alternativa para inicializar coleções.

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

Parece ser o mesmo que a palavra-chave with, tão popular no flash e no vbscript. É um método de mudar o que thisé e nada mais.

Chuck Vose
fonte
Na verdade não. Seria como dizer que criar uma nova classe é um método para mudar o que thisé. A sintaxe apenas cria uma classe anônima (portanto, qualquer referência thisestaria se referindo ao objeto dessa nova classe anônima) e, em seguida, usa um bloco {...}inicializador para inicializar a instância recém-criada.
18713