method inner classes java anonymous-class

java - inner - ¿Cuál es el daño al usar la clase Anónimo?



method-local inner class (6)

Debido a que no necesita una subclase separada, solo necesita crear una nueva ArrayList de la clase normal y addAll() las listas en ella.

Al igual que:

public static List<String> addLists (List<String> a, List<String> b) { List<String> results = new ArrayList<String>(); results.addAll( a); results.addAll( b); return results; }

Es malo crear una subclase, eso no es necesario. No es necesario extender el comportamiento de la subclase, simplemente cambie los valores de los datos .

La pregunta surgió al leer una respuesta a esta pregunta: ¿Cómo unir dos listas en java ? Esta answer dio la solución

List<String> newList = new ArrayList<String>() { { addAll(listOne); addAll(listTwo); } };

Al leer los comentarios, los usuarios dijeron que era malo y feo y que no deberían usarse en producción.

Me gustaría saber cuál es el daño al usar esto? ¿Por qué es feo, malo o malo usar en producción?

Nota: se le preguntó como una pregunta porque, la publicación a la que se hace referencia es demasiado antigua (2008) y el que responde está ausente desde hace varios meses.


En su ejemplo, realmente se ve malvado y feo , al menos para mí, es difícil entender lo que está sucediendo en el código. Pero hay algunos patrones de uso de clases anónimas a las que las personas están acostumbradas porque los ven muy a menudo, por ejemplo

Arrays.sort(args, new Comparator<String>() { public int compare(String o1, String o2) { return ... }});

Yo llamaría al anterior un caso de mejores prácticas.


Este uso particular de clases anónimas tiene varios problemas:

  1. es un idioma poco conocido. Los desarrolladores que no lo conocen (o lo saben y no lo usan mucho) se ralentizarán al leer y / o modificar el código que lo usa.
  2. en realidad está haciendo un uso indebido de una función de idioma: no está tratando de definir un nuevo tipo de ArrayList , solo quiere una lista de arreglos con algunos valores existentes en ella
  3. crea una nueva clase que toma recursos: espacio en disco para contener la definición de clase, tiempo para analizar / verificar / ..., permgen para contener la definición de clase, ...
  4. incluso si el "código real" es un poco más largo, se puede mover fácilmente a un método de utilidad con nombre joinLists(listOne, listTwo) )

En mi opinión, el # 1 es la razón más importante para evitarlo, seguido de cerca por el # 2. # 3 generalmente no es un gran problema, pero no debe olvidarse.


Excepto por los problemas ya mencionados con respecto al buen uso del estilo de programación y de la herencia, existe otro problema más sutil: las clases internas y las instancias de clases anónimas (no estáticas) actúan como cierres. Esto significa que mantienen una referencia implícita a la instancia de clase adjunta . Esto puede resultar en la prevención de la recolección de basura y, al final, una pérdida de memoria.

Dado un ejemplo de pieza de código fuente:

public interface Inner { void innerAction(); } public class Outer { public void methodInOuter() {} private Inner inner = new Inner() { public void innerAction() { // calling a method outside of scope of this anonymous class methodInOuter(); } } }

Lo que ocurre en el momento de la compilación es que el compilador crea un archivo de clase para la nueva subclase anónima de Inner que obtiene un campo denominado sintético con la referencia a la instancia de la clase Outer . El bytecode generado será más o menos equivalente a algo como esto:

public class Outer$1 implements Inner { private final Outer outer; // synthetic reference to enclosing instance public Outer$1(Outer outer) { this.outer = outer; } public void innerAction() { // the method outside of scope is called through the reference to Outer outer.methodInOuter(); } }

Tal captura de referencia a la instancia adjunta ocurre incluso para las clases anónimas que en realidad nunca tienen acceso a ninguno de los métodos o campos de la clase adjunta , como la lista doble inicialización (DBI) en su pregunta.

Esto resulta en el hecho de que la lista DBI mantiene una referencia a la instancia adjunta, siempre que exista, evitando que la instancia adjunta sea recogida de basura. Supongamos que la lista DBI pasa a vivir durante mucho tiempo en la aplicación, por ejemplo, como parte del modelo en el patrón MVC, y la clase adjunta capturada es, por ejemplo, un JFrame , que es una clase bastante grande con muchos campos. Si creó un par de listas DBI, obtendría una pérdida de memoria muy rápidamente.

Una solución posible sería usar DBI solo en métodos estáticos , porque no existe tal instancia de inclusión disponible en su alcance.

Por otro lado, seguiría argumentando que el uso de DBI aún no es necesario en la mayoría de los casos. En cuanto a la unión de listas, crearía un método reutilizable simple, que no solo es más seguro, sino también más conciso y claro.

public static <T> List<T> join(List<? extends T> first, List<? extends T> second) { List<T> joined = new ArrayList<>(); joined.addAll(first); joined.addAll(second); return joined; }

Y luego el código del cliente se convierte simplemente:

List<String> newList = join(listOne, listTwo);

Lectura adicional: https://.com/a/924536/1064809


Los comentarios "feo" y "no usar en producción" se refieren a este uso específico de clases anónimas, no a clases anónimas en general.

Este uso específico asigna a newList una subclase anónima de ArrayList<String> , una clase completamente nueva creada con un único propósito en mente: inicializar una lista con el contenido de dos listas específicas. Esto no es muy legible (incluso un lector experimentado podría dedicar unos segundos a descifrarlo), pero lo más importante es que puede lograrse sin crear subclases en el mismo número de operaciones.

Básicamente, la solución paga por una pequeña conveniencia al crear una nueva subclase, lo que puede ocasionar problemas en el futuro, por ejemplo, en situaciones en las que intenta persistir en esta colección mediante un marco automatizado que espera que las colecciones tengan tipos específicos.


No es un mal enfoque per se, por ejemplo, en el rendimiento o algo por el estilo, pero el enfoque es un poco oscuro y al usar algo como esto, siempre (digamos, 99%) tiene que explicar este enfoque. Creo que esa es una de las principales razones para no usar este enfoque, y al escribir:

List<String> newList = new ArrayList<String>(); newList.addAll(listOne); newList.addAll(listTwo);

es un poco más tipado, es un poco más fácil de leer, lo que ayuda mucho a comprender o depurar el código.