¿Qué es la inicialización de Double Brace en Java?
initialization double-brace-initialize (13)
¿Quieres decir algo como esto?
List<String> blah = new ArrayList<String>(){{add("asdfa");add("bbb");}};
es una matriz de inicialización de lista en tiempo de creación (hack)
¿Qué es la sintaxis de inicialización de Double Brace ( {{ ... }}
) en Java?
Como lo señaló @Lukas Eder, se debe evitar la inicialización de las llaves dobles.
Crea una clase interna anónima, y dado que todas las clases internas mantienen una referencia a la instancia principal, puede, y el 99% probablemente, evitará la recolección de basura si estos objetos de colección son referenciados por más objetos que solo el que declara.
Java 9 ha introducido los métodos de conveniencia List.of
, Set.of
y Map.of
, que deberían usarse en su lugar. Son más rápidos y más eficientes que el inicializador de doble refuerzo.
Creo que es importante subrayar que no existe tal cosa como la "inicialización de doble refuerzo" en Java . El sitio web de Oracle no tiene este término. En este ejemplo, se usan dos características juntas: clase anónima y bloque de inicialización. Parece que los desarrolladores han olvidado el antiguo bloque de inicialización y causan cierta confusión en este tema. Cita de documentos de Oracle :
Los bloques de inicialización para las variables de instancia parecen bloques de inicialización estáticos, pero sin la palabra clave estática:
{
// whatever code is needed for initialization goes here
}
Es, entre otros usos, un atajo para inicializar colecciones. Aprende más ...
Esto parece ser lo mismo que la palabra clave tan popular en flash y vbscript. Es un método para cambiar lo que es this
y nada más.
La inicialización de las llaves dobles crea una clase anónima derivada de la clase especificada (las llaves externas ) y proporciona un bloque inicializador dentro de esa clase (las llaves internas ). p.ej
new ArrayList<Integer>() {{
add(1);
add(2);
}};
Tenga en cuenta que el efecto de usar esta inicialización de doble refuerzo es que está creando clases internas anónimas. La clase creada tiene un puntero implícito a la clase externa circundante. Aunque normalmente no es un problema, puede causar problemas en algunas circunstancias, por ejemplo, al serializar o recolectar basura, y vale la pena tenerlo en cuenta.
La inicialización de doble refuerzo aprovecha la sintaxis de la clase interna. Supongamos que desea construir una lista de matriz y pasarla a un método:
ArrayList<String> friends = new ArrayList<>();
friends.add("Mark");
friends.add("Steve");
invite(friends);
Si no necesita la lista de arreglos nuevamente, sería bueno hacerla anónima. Pero entonces, ¿cómo puedes agregar los elementos? (La inicialización de doble refuerzo viene aquí) Aquí es cómo:
invite(new ArrayList<String>({{ add("Mark"); add("Steve");}});
Tenga en cuenta las llaves dobles. Las llaves externas forman una subclase anónima de ArrayList
. Los tirantes interiores son un bloque de construcción de objetos .
Me gustaría señalar que no hay tal cosa como la inicialización de doble refuerzo. Solo hay un bloque tradicional de inicialización tradicional. El segundo bloque de llaves no tiene nada que ver con la inicialización. Las respuestas dicen que esas dos llaves inician algo, pero no es así.
En segundo lugar, casi todas las respuestas dicen que se usa para crear clases internas anónimas. Creo que las personas que lean 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. Al leer esas respuestas, parece que hay un nuevo futuro especial dedicado a las clases anónimas y creo que eso es engañoso.
Yendo más allá, esta pregunta habla sobre la situación cuando el segundo soporte de apertura está justo después del primer soporte de apertura. Cuando se usa en la clase normal, generalmente hay algo de código entre dos llaves, pero es totalmente lo mismo. Así que es una cuestión de colocar corchetes. Así que creo que no deberíamos decir que esto es algo nuevo y emocionante, porque esto es lo que todos sabemos, pero solo está escrito con algún código entre paréntesis. No debemos crear un nuevo concepto llamado "inicialización de doble refuerzo".
No estoy de acuerdo con un argumento de que crees demasiadas clases anónimas. No los estás creando debido a un bloque de inicialización, sino simplemente porque los creas. Se crearían incluso si no utilizara la inicialización de dos llaves, por lo que esos problemas se producirían incluso sin la inicialización ... La inicialización no es el factor que crea el objeto inicializado.
Además, no deberíamos hablar sobre el problema creado mediante el uso de esta "inicialización de abrazadera doble" inexistente o incluso por la inicialización de un corchete normal, porque los problemas descritos existen solo 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 refuerzo".
Para evitar todos los efectos negativos de la inicialización de doble refuerzo, como:
- Broken "es igual a" la compatibilidad.
- No se realizan comprobaciones, cuando se utilizan asignaciones directas.
- Posibles pérdidas de memoria.
hacer las siguientes cosas:
- Haga la clase "Generador" por separado, especialmente para la inicialización de llaves dobles.
- Declara los campos con valores por defecto.
- Ponga el método de creación de objetos en esa clase.
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:
- Simplemente para usar.
- No rompa "es igual" a la compatibilidad.
- Puede realizar comprobaciones en el método de creación.
- No hay pérdidas de memoria.
Desventajas:
- Ninguna.
Y, como resultado, tenemos el patrón de construcción de Java más simple de todos los tiempos.
Ver todos los ejemplos en github: java-sf-builder-simple-example
Para una aplicación divertida de inicialización de doble refuerzo, vea aquí Dwemthy''s Array 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!
Puede poner algunas declaraciones de 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));
}
};
Pero este caso afecta al rendimiento, revisa esta discussion
Cada vez que alguien usa la inicialización de doble refuerzo, un gatito muere.
Aparte de que la sintaxis es bastante inusual y no es realmente idiomática (el sabor es discutible, por supuesto), está creando innecesariamente dos problemas significativos en su aplicación, que recientemente he publicado en el blog con más detalle aquí .
1. Estás creando demasiadas clases anónimas
Cada vez que se utiliza la inicialización de doble refuerzo 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 un poco de sobrecarga para su cargador de clases, ¡para nada! Por supuesto, no tomará mucho tiempo de inicialización si lo haces una vez. Pero si lo hace 20''000 veces en su aplicación empresarial ... ¿todo ese montón de memoria solo por un poco de "sintaxis de azúcar"?
2. ¡Estás creando potencialmente una pérdida de memoria!
Si toma el código anterior y devuelve ese mapa desde un método, las personas que llaman a ese método podrían estar reteniendo sin sospecha recursos muy pesados que no pueden ser recolectados como 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 Map
devuelto ahora contendrá una referencia a la instancia ReallyHeavyObject
de ReallyHeavyObject
. Probablemente no quieras arriesgarte a que:
Imagen de http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/
3. Puedes pretender que Java tiene literales de mapas.
Para responder a su pregunta real, las personas han estado usando esta sintaxis para pretender que Java tiene algo así como los 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.
- La primera llave crea una nueva Clase Interna Anónima.
- El segundo conjunto de llaves crea los inicializadores de una instancia como un bloque estático en Clase.
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
Primera llave crea una nueva Clase Interna Anónima. Estas clases internas son capaces de acceder al comportamiento de su clase padre. Entonces, en nuestro caso, en realidad estamos creando una subclase de clase HashSet, por lo que esta clase interna es capaz de usar el método add ().
Y el segundo conjunto de llaves no son más que inicializadores de instancia. Si recuerda los conceptos básicos de Java, entonces puede asociar fácilmente los bloques de inicialización de instancia con los inicializadores estáticos debido a llaves similares 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.