O que é a boot do Double Brace em Java?

O que é a syntax de boot do Double Brace ( {{ ... }} ) em Java?

A boot com chave dupla cria uma class anônima derivada da class especificada (as chaves externas ) e fornece um bloco inicializador dentro dessa class (as chaves internas ). por exemplo

 new ArrayList() {{ add(1); add(2); }}; 

Observe que um efeito de usar essa boot de chave dupla é que você está criando classs internas anônimas. A class criada tem um ponteiro implícito para a class externa circundante. Embora normalmente não seja um problema, pode causar sofrimento em algumas circunstâncias, por exemplo, quando serializando ou garbage collection, e vale a pena estar ciente disso.

Toda vez que alguém usa boot com chave dupla, um gatinho é morto.

Além da syntax ser bastante incomum e não idiomática (o sabor é discutível, é claro), você está criando desnecessariamente dois problemas significativos em seu aplicativo, sobre os quais eu acabei de escrever recentemente com mais detalhes aqui .

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

Cada vez que você usa a boot com chave dupla, uma nova class é 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á essas classs:

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

Isso é um pouco sobrecarga para o seu classloader – por nada! Claro que não vai demorar muito tempo de boot, se você fizer isso uma vez. Mas se você fizer isso 20.000 vezes em todo o seu aplicativo corporativo … toda essa memory de heap apenas para um pouco de “açúcar de syntax”?

2. Você está potencialmente criando um memory leaks!

Se você pegar o código acima e retornar esse mapa de um método, os chamadores desse método podem estar desprevenidos com resources muito pesados ​​que não podem ser coletados como 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 Map retornado agora conterá uma referência à instância delimitadora de ReallyHeavyObject . Você provavelmente não quer arriscar isso:

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 tem literais de mapa

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

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

Algumas pessoas podem achar isto sintaticamente estimulante.

  • A primeira chave cria uma nova class interna anônima.
  • O segundo conjunto de chaves cria um inicializador de instâncias como o bloco estático na class.

Por exemplo:

  public class TestHashMap { public static void main(String[] args) { HashMap map = new HashMap(){ { put("1", "ONE"); }{ put("2", "TWO"); }{ put("3", "THREE"); } }; Set keySet = map.keySet(); for (String string : keySet) { System.out.println(string+" ->"+map.get(string)); } } } 

Como funciona

A primeira chave cria uma nova class interna anônima. Essas classs internas são capazes de acessar o comportamento de sua class pai. Então, no nosso caso, estamos criando uma subclass da class HashSet, então essa class interna é capaz de usar o método add ().

E o segundo conjunto de chaves não são nada além de inicializadores de instância. Se você lembrar os conceitos básicos do Java, poderá associar facilmente os blocos do inicializador de instâncias aos inicializadores estáticos, devido à chave semelhante à estrutura. A única diferença é que o inicializador estático é adicionado à palavra-chave estática e é executado apenas uma vez; não importa quantos objects você crie.

Mais

Para uma aplicação divertida de boot de chave dupla, veja aqui o Array de Dwemthy 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 que é importante ressaltar que não existe algo como “boot Double Brace” em Java . O site da Oracle não possui este termo. Neste exemplo, há dois resources usados ​​juntos: class anônima e bloco inicializador. Parece que o antigo bloco inicializador foi esquecido pelos desenvolvedores e causou alguma confusão neste tópico. Citação dos documentos do Oracle :

Os blocos inicializadores para variables ​​de instância são parecidos com os blocos do inicializador estático, mas sem a palavra-chave estática:

 { // whatever code is needed for initialization goes here } 

Gostaria de salientar que não existe boot de chave dupla. Existe apenas um bloco de boot tradicional normal de uma chave. O segundo bloco de chaves não tem nada a ver com a boot. As respostas dizem que essas duas chaves inicializam algo, mas não é assim.

Em segundo lugar, quase todas as respostas falam que é uma coisa usada na criação de classs internas anônimas. Acho que as pessoas que lêem essas respostas terão a impressão de que isso é usado apenas na criação de classs internas anônimas. Mas é usado em todas as classs. Lendo essas respostas, parece que há algum novo futuro especial dedicado às aulas anônimas e acho isso enganoso.

Indo mais longe, esta questão fala sobre a situação quando o segundo suporte de abertura é logo após o primeiro suporte de abertura. Quando usado na class normal, geralmente há algum código entre duas chaves, mas é totalmente a mesma coisa. Então é uma questão de colocar parênteses. Então eu acho que não devemos dizer que isso é uma coisa nova e excitante, porque essa é a coisa que todos nós sabemos, mas apenas escrita com algum código entre parênteses. Não devemos criar um novo conceito chamado “boot de chave dupla”.

Eu não concordo com um argumento que você cria muitas classs anônimas. Você não está criando-os porque um bloco de boot, mas apenas porque você os cria. Eles seriam criados mesmo se você não usasse a boot de duas chaves para que esses problemas ocorressem mesmo sem boot … A boot não é o fator que cria o object inicializado.

Além disso, não devemos falar sobre o problema criado usando essa coisa inexistente “boot de chave dupla” ou até mesmo com a boot normal de um colchete, porque os problemas descritos existem apenas por causa da criação de classs anônimas, portanto 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 criar classs anônimas, mas essa coisa malvada (inexistente) chamada “boot de chave dupla”.

Para evitar todos os efeitos negativos da boot de chave dupla, como:

  1. Quebrado “igual” compatibilidade.
  2. Nenhuma verificação executada, quando usar atribuições diretas.
  3. Possíveis vazamentos de memory.

faça as próximas coisas:

  1. Faça uma class “Construtor” separada, especialmente para a boot de chave dupla.
  2. Declare campos com valores padrão.
  3. Coloque o método de criação de object nessa class.

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 usar.
  2. Não quebra a compatibilidade “igual”.
  3. Você pode executar verificações no método de criação.
  4. Nenhuma memory vaza.

Desvantagens:

  • Nenhum.

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

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

É – entre outros usos – um atalho para inicializar collections. Saber mais …

você quer dizer algo assim?

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

é uma boot de lista de array no momento da criação (hack)

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

 List characters = new ArrayList() { { for (char c = 'A'; c <= 'E'; c++) add(c); } }; 

 Random rnd = new Random(); List integers = new ArrayList() { { while (size() < 10) add(rnd.nextInt(1_000_000)); } }; 

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

Isso parece ser o mesmo que o com a palavra-chave tão popular em flash e vbscript. É um método de mudar o que é this e nada mais.