¿Qué es la sintaxis de inicialización Double Brace ( {{ ... }}
) en Java?
¿Qué es la sintaxis de inicialización Double Brace ( {{ ... }}
) en Java?
Respuestas:
La inicialización de doble llave crea una clase anónima derivada de la clase especificada (las llaves externas ) y proporciona un bloque de inicializador dentro de esa clase (las llaves internas ). p.ej
new ArrayList<Integer>() {{
add(1);
add(2);
}};
Tenga en cuenta que un efecto del uso de esta inicialización de doble paréntesis es que está creando clases internas anónimas. La clase creada tiene un this
puntero implícito a la clase externa circundante. Aunque normalmente no es un problema, puede causar dolor en algunas circunstancias, por ejemplo, cuando se serializa o se recolecta basura, y vale la pena tenerlo en cuenta.
Cada vez que alguien usa la inicialización de doble llave, un gatito es asesinado.
Además de que la sintaxis es bastante inusual y no es realmente idiomática (el sabor es discutible, por supuesto), está creando innecesariamente dos problemas importantes en su aplicación, sobre los que acabo de bloguear recientemente con más detalle aquí .
Cada vez que utiliza la inicialización de doble paréntesis, se crea una nueva clase. Por ejemplo, este ejemplo:
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");
}});
}});
}};
... producirá estas clases:
Test$1$1$1.class
Test$1$1$2.class
Test$1$1.class
Test$1.class
Test.class
Eso es bastante sobrecarga para tu cargador de clases, ¡para nada! Por supuesto, no tomará mucho tiempo de inicialización si lo hace una vez. Pero si hace esto 20,000 veces en toda su aplicación empresarial ... ¿toda esa memoria de almacenamiento dinámico solo por un poco de "azúcar de sintaxis"?
Si toma el código anterior y devuelve ese mapa de un método, las personas que llaman de ese método podrían estar aferrándose a recursos muy pesados que no se pueden recolectar basura. Considere el siguiente ejemplo:
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;
}
}
El devuelto Map
ahora contendrá una referencia a la instancia adjunta de ReallyHeavyObject
. Probablemente no quiera arriesgarse a que:
Imagen de http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/
Para responder a su pregunta real, las personas han estado utilizando esta sintaxis para pretender que Java tiene algo así como literales de mapas, similar a los literales de matriz existentes:
String[] array = { "John", "Doe" };
Map map = new HashMap() {{ put("John", "Doe"); }};
Algunas personas pueden encontrar esto sintácticamente estimulante.
{{...}}
y se declara como un static
campo, no debería haber una posible pérdida de memoria, solo una clase anónima y ninguna referencia de instancia adjunta, ¿verdad?
Map.of()
para ese propósito, por lo que será una mejor solución
ReallyHeavyObject
. Además, las clases internas anónimas capturan todas las variables locales utilizadas dentro del cuerpo de la clase, por lo que si usa no solo constantes para inicializar colecciones o mapas con este patrón, las instancias de la clase interna las capturarán todas y continuarán haciendo referencia a ellas incluso cuando realmente se eliminen de La colección o el mapa. En ese caso, estas instancias no solo necesitan el doble de memoria necesaria para las referencias, sino que tienen otra pérdida de memoria en ese sentido.
Por ejemplo:
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));
}
}
}
Cómo funciona
La primera llave crea una nueva clase interna anónima. Estas clases internas son capaces de acceder al comportamiento de su clase principal. Entonces, en nuestro caso, en realidad estamos creando una subclase de la clase HashSet, por lo que esta clase interna es capaz de usar el método put ().
Y el segundo conjunto de llaves no es más que inicializadores de instancia. Si recuerda los conceptos básicos de Java, puede asociar fácilmente los bloques de inicializador de instancia con inicializadores estáticos debido a un paréntesis similar como struct. La única diferencia es que el inicializador estático se agrega con una palabra clave estática y se ejecuta solo una vez; no importa cuántos objetos crees.
Para una aplicación divertida de inicialización de doble paréntesis, vea aquí la matriz de Dwemthy en Java .
Un experto
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;
}}
Y ahora, prepárate para el BattleOfGrottoOfSausageSmells
y ... ¡tocino grueso!
Creo que es importante enfatizar que no existe la "inicialización de doble paréntesis" en Java . El sitio web de Oracle no tiene este término. En este ejemplo, hay dos características utilizadas juntas: clase anónima y bloque inicializador. Parece que los desarrolladores han olvidado el antiguo bloque inicializador y causan cierta confusión en este tema. Cita de documentos de Oracle :
Los bloques de inicializador para las variables de instancia se parecen a los bloques de inicializador estático, pero sin la palabra clave estática:
{
// whatever code is needed for initialization goes here
}
1- No existe el uso de llaves dobles:
me gustaría señalar que no existe la inicialización de llaves dobles . Solo hay un bloque de inicialización de una llave tradicional tradicional. El segundo bloque de llaves no tiene nada que ver con la inicialización. Las respuestas dicen que esas dos llaves inicializan algo, pero no es así.
2- No se trata solo de clases anónimas sino de todas las clases:
casi todas las respuestas hablan de que es algo que se usa al crear clases internas anónimas. Creo que las personas que leen esas respuestas tendrán la impresión de que esto solo se usa al crear clases internas anónimas. Pero se usa en todas las clases. Parece que leer esas respuestas es una característica especial completamente nueva dedicada a clases anónimas y creo que es engañoso.
3- El propósito es solo colocar los corchetes uno detrás del otro, no es un concepto nuevo:
más allá, esta pregunta habla sobre la situación cuando el segundo corchete de apertura es justo después del primer corchete de apertura. Cuando se usa en la clase normal, generalmente hay algún código entre dos llaves, pero es totalmente lo mismo. Entonces se trata de colocar corchetes. Así que creo que no deberíamos decir que esto es algo nuevo y emocionante, porque es lo que todos sabemos, pero solo escrito con algún código entre paréntesis. No debemos crear un nuevo concepto llamado "inicialización de doble paréntesis".
4- Crear clases anónimas anidadas no tiene nada que ver con dos llaves:
no estoy de acuerdo con el argumento de que creas demasiadas clases anónimas. No los está creando porque es un bloque de inicialización, sino simplemente porque los creó. Se crearían incluso si no utilizara la inicialización de dos llaves, por lo que esos problemas ocurrirían incluso sin inicialización ... La inicialización no es el factor que crea los objetos inicializados.
Además, no deberíamos hablar sobre el problema creado mediante el uso de esta "inicialización de doble paréntesis" inexistente o incluso por la inicialización normal de un paréntesis, porque los problemas descritos solo existen debido a la creación de una clase anónima, por lo que no tiene nada que ver con la pregunta original. Pero todas las respuestas dan a los lectores la impresión de que no es culpa de crear clases anónimas, sino de esta cosa malvada (inexistente) llamada "inicialización de doble paréntesis".
Para evitar todos los efectos negativos de la inicialización de doble paréntesis, como:
hacer las siguientes cosas:
Ejemplo:
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();
Ventajas:
Desventajas
Y, como resultado, tenemos el patrón de generador de Java más simple que existe.
Ver todas las muestras en github: java-sf-builder-simple-example
Es, entre otros usos, un atajo para inicializar colecciones. Aprende más ...
Puede poner algunas declaraciones Java como bucle para inicializar la colección:
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 se señaló en @Lukas Eder, se debe evitar la doble inicialización de colecciones.
Crea una clase interna anónima, y dado que todas las clases internas mantienen una referencia a la instancia principal, puede, y probablemente el 99% lo hará, evitar la recolección de basura si se hace referencia a estos objetos de recolección por más objetos que solo el declarante.
Java 9 ha introducido métodos de conveniencia List.of
, Set.of
y Map.of
que se deben utilizar en su lugar. Son más rápidos y más eficientes que el inicializador de doble paréntesis.
La primera llave crea una nueva clase anónima y el segundo conjunto de llaves crea una instancia de inicializadores como el bloque estático.
Como otros han señalado, no es seguro de usar.
Sin embargo, siempre puede usar esta alternativa para inicializar colecciones.
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
List<String> list = List.of("A", "B", "C");
Esto parece ser lo mismo que con la palabra clave tan popular en flash y vbscript. Es un método para cambiar lo que this
es y nada más.
this
es. La sintaxis solo crea una clase anónima (por lo que cualquier referencia a this
se referiría al objeto de esa nueva clase anónima), y luego usa un bloque {...}
inicializador para inicializar la instancia recién creada.