O que é a sintaxe de inicialização do Double Brace ( {{ ... }}
) em Java?
O que é a sintaxe de inicialização do Double Brace ( {{ ... }}
) em Java?
Respostas:
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 this
ponteiro 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.
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 .
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"?
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 Map
agora conterá uma referência à instância anexa de ReallyHeavyObject
. Você provavelmente não quer arriscar que:
Imagem de http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/
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.
{{...}}
e declarado como um static
campo, 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?
Map.of()
para o efeito, de modo que vai ser uma solução melhor
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.
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.
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 BattleOfGrottoOfSausageSmells
e… bacon robusto!
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
}
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".
Para evitar todos os efeitos negativos da inicialização entre chaves, como:
faça as próximas coisas:
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:
Desvantagens:
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
É - entre outros usos - um atalho para inicializar coleções. Saber mais ...
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));
}
};
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.of
e Map.of
, que devem ser usados em seu lugar. Eles são mais rápidos e eficientes do que o inicializador de cinta dupla.
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.
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
List<String> list = List.of("A", "B", "C");
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.
this
é. A sintaxe apenas cria uma classe anônima (portanto, qualquer referência this
estaria se referindo ao objeto dessa nova classe anônima) e, em seguida, usa um bloco {...}
inicializador para inicializar a instância recém-criada.