initialize - ¿Eficiencia de la Java “Double Brace Initialization”?
java how to initialize a list (15)
La inicialización de doble refuerzo es un truco innecesario que puede introducir fugas de memoria y otros problemas
No hay una razón legítima para usar este "truco". Guava proporciona colecciones inmutables que incluyen tanto fábricas estáticas como constructores, lo que le permite llenar su colección donde se declara en una sintaxis limpia, legible y segura .
El ejemplo en la pregunta se convierte en:
Set<String> flavors = ImmutableSet.of(
"vanilla", "strawberry", "chocolate", "butter pecan");
No solo es más corto y fácil de leer, sino que evita los numerosos problemas con el patrón de doble refuerzo descrito en otras respuestas . Claro, su desempeño es similar al de construcción directa HashMap
, pero es peligroso y propenso a errores, y hay mejores opciones.
Cada vez que considere la posibilidad de realizar una inicialización con doble refuerzo, debe volver a examinar sus API o introducir otras nuevas para abordar el problema adecuadamente, en lugar de aprovechar los trucos sintácticos.
Error-Prone ahora marca este anti-patrón .
En Hidden Features of Java, la respuesta principal menciona la inicialización de doble refuerzo , con una sintaxis muy atractiva:
Set<String> flavors = new HashSet<String>() {{
add("vanilla");
add("strawberry");
add("chocolate");
add("butter pecan");
}};
Este idioma crea una clase interna anónima con solo un inicializador de instancia, que "puede usar cualquier [...] método en el ámbito que lo contiene".
Pregunta principal: ¿Es esto tan ineficiente como parece? ¿Debería su uso estar limitado a inicializaciones únicas? (Y por supuesto presumiendo!)
Segunda pregunta: el nuevo HashSet debe ser el "esto" usado en el inicializador de instancia ... ¿alguien puede arrojar luz sobre el mecanismo?
Tercera pregunta: ¿Es este lenguaje demasiado oscuro para usar en el código de producción?
Resumen: Muy, muy buenas respuestas, gracias a todos. En la pregunta (3), la gente sintió que la sintaxis debería ser clara (aunque recomendaría un comentario ocasional, especialmente si su código se transmite a los desarrolladores que no estén familiarizados con él).
En la pregunta (1), el código generado debe ejecutarse rápidamente. Los archivos .class adicionales causan el desorden de archivos jar, y retrasan ligeramente el inicio del programa (gracias a @coobird por medirlo). @Thilo señaló que la recolección de basura puede verse afectada, y el costo de memoria para las clases con carga adicional puede ser un factor en algunos casos.
La pregunta (2) resultó ser la más interesante para mí. Si entiendo las respuestas, lo que está sucediendo en DBI es que la clase interna anónima extiende la clase del objeto que está construyendo el nuevo operador, y por lo tanto tiene un valor "this" que hace referencia a la instancia que se está construyendo. Muy aseado.
En general, DBI me parece una especie de curiosidad intelectual. Coobird y otros señalan que puede lograr el mismo efecto con Arrays.asList, los métodos de varargs, Google Collections y los literales propuestos de Java 7 Collection. Los nuevos lenguajes JVM como Scala, JRuby y Groovy también ofrecen notaciones concisas para la construcción de listas, e interoperan bien con Java. Dado que el DBI desordena el classpath, ralentiza la carga de la clase un poco y hace que el código sea un poco más oscuro, probablemente lo evito. Sin embargo, tengo la intención de explicar esto a un amigo que acaba de obtener su SCJP y que ama las buenas intenciones sobre la semántica de Java. ;-) ¡Gracias a todos!
7/2017: Baeldung tiene un buen resumen de la inicialización de doble refuerzo y lo considera un antipatrón.
12/2017: @Basil Bourque señala que en el nuevo Java 9 puede decir:
Set<String> flavors = Set.of("vanilla", "strawberry", "chocolate", "butter pecan");
Eso es seguro el camino a seguir. Si está atascado con una versión anterior, eche un vistazo a ImmutableSet de Google Collections .
Esto llamará
add()
a cada miembro. Si puedes encontrar una forma más eficiente de colocar elementos en un conjunto hash, entonces úsalo. Tenga en cuenta que la clase interna probablemente generará basura, si es sensible al respecto.Me parece que el contexto es el objeto devuelto por
new
, que es elHashSet
.Si necesita preguntar ... Lo más probable es que las personas que lo buscan lo sepan o no. ¿Es fácil de entender y explicar? Si puede responder "sí" a ambos, siéntase libre de usarlo.
Aquí está el problema cuando me dejo llevar por clases internas anónimas:
2009/05/27 16:35 1,602 DemoApp2$1.class
2009/05/27 16:35 1,976 DemoApp2$10.class
2009/05/27 16:35 1,919 DemoApp2$11.class
2009/05/27 16:35 2,404 DemoApp2$12.class
2009/05/27 16:35 1,197 DemoApp2$13.class
/* snip */
2009/05/27 16:35 1,953 DemoApp2$30.class
2009/05/27 16:35 1,910 DemoApp2$31.class
2009/05/27 16:35 2,007 DemoApp2$32.class
2009/05/27 16:35 926 DemoApp2$33$1$1.class
2009/05/27 16:35 4,104 DemoApp2$33$1.class
2009/05/27 16:35 2,849 DemoApp2$33.class
2009/05/27 16:35 926 DemoApp2$34$1$1.class
2009/05/27 16:35 4,234 DemoApp2$34$1.class
2009/05/27 16:35 2,849 DemoApp2$34.class
/* snip */
2009/05/27 16:35 614 DemoApp2$40.class
2009/05/27 16:35 2,344 DemoApp2$5.class
2009/05/27 16:35 1,551 DemoApp2$6.class
2009/05/27 16:35 1,604 DemoApp2$7.class
2009/05/27 16:35 1,809 DemoApp2$8.class
2009/05/27 16:35 2,022 DemoApp2$9.class
Estas son todas las clases que se generaron cuando estaba haciendo una aplicación simple, y usé grandes cantidades de clases internas anónimas; cada clase se compilará en un archivo de class
separado.
La "inicialización de doble refuerzo", como ya se mencionó, es una clase interna anónima con un bloque de inicialización de instancia, lo que significa que se crea una nueva clase para cada "inicialización", todo con el propósito de crear un único objeto.
Teniendo en cuenta que la Máquina Virtual de Java tendrá que leer todas esas clases cuando las use, eso puede llevar a algún tiempo en el proceso de verificación de bytecode y tal. Sin mencionar el aumento en el espacio en disco necesario para almacenar todos esos archivos de class
.
Parece como si hubiera un poco de sobrecarga al utilizar la inicialización con doble refuerzo, por lo que probablemente no sea una buena idea ir demasiado por la borda. Pero como Eddie ha señalado en los comentarios, no es posible estar absolutamente seguro del impacto.
Sólo como referencia, la inicialización de doble refuerzo es la siguiente:
List<String> list = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
Parece una característica "oculta" de Java, pero es solo una reescritura de:
List<String> list = new ArrayList<String>() {
// Instance initialization block
{
add("Hello");
add("World!");
}
};
Así que básicamente es un bloque de inicialización de instancia que forma parte de una clase interna anónima .
La propuesta de Joshua Bloch Collection Literals para el Proyecto Coin estuvo en la línea de:
List<Integer> intList = [1, 2, 3, 4];
Set<String> strSet = {"Apple", "Banana", "Cactus"};
Map<String, Integer> truthMap = { "answer" : 42 };
Lamentablemente, no llegó ni a Java 7 ni a 8, y fue archivado indefinidamente.
Experimentar
Aquí está el experimento simple que he probado: ¡haga 1000 ArrayList
s con los elementos "Hello"
y "World!"
añadido a ellos a través del método add
, utilizando los dos métodos:
Método 1: Inicialización de doble refuerzo
List<String> l = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
Método 2: crear una instancia de ArrayList
y add
List<String> l = new ArrayList<String>();
l.add("Hello");
l.add("World!");
Creé un programa simple para escribir un archivo fuente Java para realizar 1000 inicializaciones usando los dos métodos:
Prueba 1:
class Test1 {
public static void main(String[] s) {
long st = System.currentTimeMillis();
List<String> l0 = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
List<String> l1 = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
/* snip */
List<String> l999 = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
System.out.println(System.currentTimeMillis() - st);
}
}
Prueba 2:
class Test2 {
public static void main(String[] s) {
long st = System.currentTimeMillis();
List<String> l0 = new ArrayList<String>();
l0.add("Hello");
l0.add("World!");
List<String> l1 = new ArrayList<String>();
l1.add("Hello");
l1.add("World!");
/* snip */
List<String> l999 = new ArrayList<String>();
l999.add("Hello");
l999.add("World!");
System.out.println(System.currentTimeMillis() - st);
}
}
Tenga en cuenta que el tiempo transcurrido para inicializar las 1000 ArrayList
y las 1000 clases internas anónimas que extienden ArrayList
se verifica utilizando System.currentTimeMillis
, por lo que el temporizador no tiene una resolución muy alta. En mi sistema Windows, la resolución es de alrededor de 15-16 milisegundos.
Los resultados para 10 corridas de las dos pruebas fueron los siguientes:
Test1 Times (ms) Test2 Times (ms)
---------------- ----------------
187 0
203 0
203 0
188 0
188 0
187 0
203 0
188 0
188 0
203 0
Como puede verse, la inicialización de doble refuerzo tiene un tiempo de ejecución notable de alrededor de 190 ms.
Mientras tanto, el tiempo de ejecución de la inicialización de ArrayList
resultó ser 0 ms. Por supuesto, la resolución del temporizador se debe tener en cuenta, pero es probable que sea inferior a 15 ms.
Por lo tanto, parece haber una diferencia notable en el tiempo de ejecución de los dos métodos. Parece que efectivamente hay algo de sobrecarga en los dos métodos de inicialización.
Y sí, se generaron 1000 archivos .class
al compilar el programa de prueba de inicialización de refuerzo doble Test1
.
Cargar muchas clases puede agregar algunos milisegundos al inicio. Si el inicio no es tan crítico y se observa la eficiencia de las clases después del inicio, no hay diferencia.
package vanilla.java.perfeg.doublebracket;
import java.util.*;
/**
* @author plawrey
*/
public class DoubleBracketMain {
public static void main(String... args) {
final List<String> list1 = new ArrayList<String>() {
{
add("Hello");
add("World");
add("!!!");
}
};
List<String> list2 = new ArrayList<String>(list1);
Set<String> set1 = new LinkedHashSet<String>() {
{
addAll(list1);
}
};
Set<String> set2 = new LinkedHashSet<String>();
set2.addAll(list1);
Map<Integer, String> map1 = new LinkedHashMap<Integer, String>() {
{
put(1, "one");
put(2, "two");
put(3, "three");
}
};
Map<Integer, String> map2 = new LinkedHashMap<Integer, String>();
map2.putAll(map1);
for (int i = 0; i < 10; i++) {
long dbTimes = timeComparison(list1, list1)
+ timeComparison(set1, set1)
+ timeComparison(map1.keySet(), map1.keySet())
+ timeComparison(map1.values(), map1.values());
long times = timeComparison(list2, list2)
+ timeComparison(set2, set2)
+ timeComparison(map2.keySet(), map2.keySet())
+ timeComparison(map2.values(), map2.values());
if (i > 0)
System.out.printf("double braced collections took %,d ns and plain collections took %,d ns%n", dbTimes, times);
}
}
public static long timeComparison(Collection a, Collection b) {
long start = System.nanoTime();
int runs = 10000000;
for (int i = 0; i < runs; i++)
compareCollections(a, b);
long rate = (System.nanoTime() - start) / runs;
return rate;
}
public static void compareCollections(Collection a, Collection b) {
if (!a.equals(b) && a.hashCode() != b.hashCode() && !a.toString().equals(b.toString()))
throw new AssertionError();
}
}
huellas dactilares
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 34 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
Dejando de lado la eficiencia, rara vez me encuentro deseando crear una colección declarativa fuera de las pruebas unitarias. Creo que la sintaxis de doble llave es muy legible.
Otra forma de lograr la construcción declarativa de listas específicamente es usar Arrays.asList(T ...)
así:
List<String> aList = Arrays.asList("vanilla", "strawberry", "chocolate");
La limitación de este enfoque es, por supuesto, que no puede controlar el tipo específico de lista que se generará.
En general, no hay nada particularmente ineficiente al respecto. En general, no le importa a la JVM que haya creado una subclase y le haya agregado un constructor; eso es algo normal y cotidiano en un lenguaje orientado a objetos. Puedo pensar en casos muy artificiales en los que podría causar una ineficiencia al hacer esto (por ejemplo, tiene un método llamado repetidamente que termina tomando una mezcla de diferentes clases debido a esta subclase, mientras que la clase ordinaria aprobada sería totalmente predecible) - en este último caso, el compilador JIT podría realizar optimizaciones que no son factibles en el primero). Pero realmente, creo que los casos en los que importará son muy artificiales.
Me gustaría ver el problema más desde el punto de vista de si quieres "saturar las cosas" con muchas clases anónimas. Como una guía general, considere usar el lenguaje no más de lo que usaría, por ejemplo, clases anónimas para manejadores de eventos.
En (2), estás dentro del constructor de un objeto, por lo que "esto" se refiere al objeto que estás construyendo. Eso no es diferente a cualquier otro constructor.
En cuanto a (3), eso realmente depende de quién mantiene su código, supongo. Si no sabe esto por adelantado, entonces un punto de referencia que sugeriría usar es "¿ve esto en el código fuente del JDK?" (en este caso, no recuerdo haber visto muchos inicializadores anónimos, y ciertamente no en los casos en que ese es el único contenido de la clase anónima). En la mayoría de los proyectos de tamaño moderado, diría que realmente va a necesitar que sus programadores comprendan la fuente JDK en algún momento u otro, por lo que cualquier sintaxis o idioma utilizado es "juego justo". Más allá de eso, diría, capacite a las personas en esa sintaxis si tiene control sobre quién mantiene el código, o comente o evite.
Estaba investigando esto y decidí hacer una prueba más profunda que la proporcionada por la respuesta válida.
Aquí está el código: https://gist.github.com/4368924
y esta es mi conclusión
Me sorprendió descubrir que en la mayoría de las pruebas de ejecución, la iniciación interna era en realidad más rápida (casi el doble en algunos casos). Cuando se trabaja con grandes números, el beneficio parece desaparecer.
Curiosamente, el caso que crea 3 objetos en el bucle pierde su beneficio se ransea antes que en los otros casos. No estoy seguro de por qué sucede esto y se deben realizar más pruebas para llegar a alguna conclusión. Crear implementaciones concretas puede ayudar a evitar la recarga de la definición de clase (si eso es lo que está sucediendo)
Sin embargo, está claro que no se observaron muchos gastos generales en la mayoría de los casos para la construcción de un solo elemento, incluso con grandes números.
Un retroceso sería el hecho de que cada una de las iniciaciones de refuerzo doble crea un nuevo archivo de clase que agrega un bloque de disco completo al tamaño de nuestra aplicación (o aproximadamente 1k cuando se comprime). Una huella pequeña, pero si se usa en muchos lugares, podría tener un impacto. Use esto 1000 veces y potencialmente está agregando un MiB completo a su aplicación, lo que puede ser relevante en un entorno integrado.
¿Mi conclusión? Puede estar bien usarlo siempre que no se abuse de él.
Déjame saber lo que piensas :)
Para crear conjuntos, puede utilizar un método de fábrica de varargs en lugar de una inicialización de doble refuerzo:
public static Set<T> setOf(T ... elements) {
return new HashSet<T>(Arrays.asList(elements));
}
La biblioteca de Google Collections tiene muchos métodos de conveniencia como este, así como un montón de otras funcionalidades útiles.
En cuanto a la oscuridad del idioma, lo encuentro y lo uso en el código de producción todo el tiempo. Estaría más preocupado por los programadores que se confunden por el lenguaje que se le permite escribir código de producción.
Respaldé la respuesta de Nat, excepto que usaría un bucle en lugar de crear e inmediatamente lanzar la Lista implícita de asList (elementos):
static public Set<T> setOf(T ... elements) {
Set set=new HashSet<T>(elements.size());
for(T elm: elements) { set.add(elm); }
return set;
}
Si bien esta sintaxis puede ser conveniente, también agrega muchas de estas referencias de $ 0 a medida que se anidan y puede ser difícil realizar una depuración en los inicializadores a menos que se establezcan puntos de interrupción en cada uno. Por esa razón, solo recomiendo usar esto para los definidores banales, especialmente para las constantes, y los lugares donde no importan las subclases anónimas (como ninguna serialización involucrada).
Tomando la siguiente clase de prueba:
public class Test {
public void test() {
Set<String> flavors = new HashSet<String>() {{
add("vanilla");
add("strawberry");
add("chocolate");
add("butter pecan");
}};
}
}
y luego descompilar el archivo de la clase, veo:
public class Test {
public void test() {
java.util.Set flavors = new HashSet() {
final Test this$0;
{
this$0 = Test.this;
super();
add("vanilla");
add("strawberry");
add("chocolate");
add("butter pecan");
}
};
}
}
Esto no me parece terriblemente ineficiente. Si estuviera preocupado por el rendimiento de algo como esto, lo perfilaría. Y su pregunta # 2 es respondida por el código anterior: Usted está dentro de un constructor implícito (y un inicializador de instancia) para su clase interna, por lo que " this
" se refiere a esta clase interna.
Sí, esta sintaxis es oscura, pero un comentario puede aclarar el uso de la sintaxis oscura. Para aclarar la sintaxis, la mayoría de las personas están familiarizadas con un bloque de inicializador estático (Inicializadores estáticos JLS 8.7):
public class Sample1 {
private static final String someVar;
static {
String temp = null;
..... // block of code setting temp
someVar = temp;
}
}
También puede usar una sintaxis similar (sin la palabra " static
") para el uso del constructor (Inicializadores de instancia JLS 8.6), aunque nunca he visto esto usado en el código de producción. Esto es mucho menos conocido.
public class Sample2 {
private final String someVar;
// This is an instance initializer
{
String temp = null;
..... // block of code setting temp
someVar = temp;
}
}
Si no tiene un constructor predeterminado, el compilador convierte el bloque de código entre {
y }
en un constructor. Con esto en mente, desenrede el código de doble corsé:
public void test() {
Set<String> flavors = new HashSet<String>() {
{
add("vanilla");
add("strawberry");
add("chocolate");
add("butter pecan");
}
};
}
El bloque de código entre las llaves más internas se convierte en un constructor por el compilador. Las llaves más externas delimitan la clase interna anónima. Para tomar este paso final de hacer todo lo que no sea anónimo:
public void test() {
Set<String> flavors = new MyHashSet();
}
class MyHashSet extends HashSet<String>() {
public MyHashSet() {
add("vanilla");
add("strawberry");
add("chocolate");
add("butter pecan");
}
}
Para propósitos de inicialización, diría que no hay gastos generales (o tan pequeños que se puedan descuidar). Sin embargo, todo uso de flavors
no irá contra HashSet
sino contra MyHashSet
. Probablemente hay una pequeña sobrecarga (y, posiblemente, insignificante) para esto. Pero una vez más, antes de que me preocupe, lo perfilaría.
Nuevamente, para su pregunta # 2, el código anterior es el equivalente lógico y explícito de la inicialización de doble refuerzo, y hace que sea obvio donde " this
" se refiere: a la clase interna que extiende HashSet
.
Si tiene preguntas sobre los detalles de los inicializadores de instancias, consulte los detalles en la documentación de JLS .
Una propiedad de este enfoque que no se ha señalado hasta ahora es que al crear clases internas, toda la clase que contiene se captura en su alcance. Esto significa que mientras su Set esté activo, retendrá un puntero a la instancia que lo contiene ( this$0
) y evitará que se recoja basura, lo que podría ser un problema.
Esto, y el hecho de que se cree una nueva clase en primer lugar, aunque un HashSet normal funcionaría bien (o incluso mejor), hace que no quiera usar este constructo (aunque realmente anhelo el azúcar sintáctico).
Segunda pregunta: el nuevo HashSet debe ser el "esto" usado en el inicializador de instancia ... ¿alguien puede arrojar luz sobre el mecanismo? Habría esperado ingenuamente que "esto" se refiriera al objeto que inicializa "sabores".
Así es como funcionan las clases internas. Obtienen el suyo propio, pero también tienen punteros a la instancia principal, por lo que también puede llamar a métodos en el objeto contenedor. En caso de un conflicto de nombres, la clase interna (en su caso HashSet) tiene prioridad, pero puede prefijar "esto" con un nombre de clase para obtener también el método externo.
public class Test {
public void add(Object o) {
}
public Set<String> makeSet() {
return new HashSet<String>() {
{
add("hello"); // HashSet
Test.this.add("hello"); // outer instance
}
};
}
}
Para ser claro en la subclase anónima que se está creando, también podría definir los métodos allí. Por ejemplo, reemplazar HashSet.add()
public Set<String> makeSet() {
return new HashSet<String>() {
{
add("hello"); // not HashSet anymore ...
}
@Override
boolean add(String s){
}
};
}
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.
propenso a fugas
Decidí participar. El impacto en el rendimiento incluye: operación del disco + descomprimir (para jarra), verificación de clase, espacio perm-gen (para JVM de punto de acceso de Sun). Sin embargo, lo peor de todo: es propenso a fugas. No puedes simplemente regresar.
Set<String> getFlavors(){
return Collections.unmodifiableSet(flavors)
}
Entonces, si el conjunto escapa a cualquier otra parte cargada por un cargador de clases diferente y se guarda una referencia allí, se filtrará todo el árbol de clases + el cargador de clases. Para evitar eso, es necesaria una copia a HashMap, new LinkedHashSet(new ArrayList(){{add("xxx);add("yyy");}})
. Ya no es tan lindo. No uso el idioma , yo, en cambio, es como new LinkedHashSet(Arrays.asList("xxx","YYY"));
Mario Gleichman describes cómo usar las funciones genéricas de Java 1.5 para simular los literales de la Lista Scala, aunque lamentablemente terminas con listas inmutables .
Él define esta clase:
package literal;
public class collection {
public static <T> List<T> List(T...elems){
return Arrays.asList( elems );
}
}
y lo usa así:
import static literal.collection.List;
import static system.io.*;
public class CollectionDemo {
public void demoList(){
List<String> slist = List( "a", "b", "c" );
List<Integer> iList = List( 1, 2, 3 );
for( String elem : List( "a", "java", "list" ) )
System.out.println( elem );
}
}
Google Collections, ahora parte de Guava apoya una idea similar para la construcción de listas. En esta entrevista , Jared Levy dice:
Las funciones más utilizadas, que aparecen en casi todas las clases de Java que escribo, son métodos estáticos que reducen la cantidad de pulsaciones repetitivas en su código Java. Es muy conveniente poder ingresar comandos como los siguientes:
Map<OneClassWithALongName, AnotherClassWithALongName> = Maps.newHashMap();
List<String> animals = Lists.immutableList("cat", "dog", "horse");
10/07/2014: Si solo pudiera ser tan simple como el de Python:
animals = [''cat'', ''dog'', ''horse'']