varargs vararg java generics memory variadic-functions heap-pollution

java - Varargs la contaminación del montón: ¿cuál es el problema?



vararg kotlin (5)

Estaba leyendo acerca de la contaminación del montón de varargs y realmente no entiendo cómo los varargs o los tipos no confiables serían responsables de los problemas que no existen sin la generosidad. De hecho, puedo reemplazar muy fácilmente

public static void faultyMethod(List<String>... l) { Object[] objectArray = l; // Valid objectArray[0] = Arrays.asList(42); String s = l[0].get(0); // ClassCastException thrown here }

con

public static void faultyMethod(String... l) { Object[] objectArray = l; // Valid objectArray[0] = 42; // ArrayStoreException thrown here String s = l[0]; }

El segundo simplemente usa la covarianza de los arreglos, que es realmente el problema aquí. (Incluso si la List<String> fuera confiable, supongo que todavía sería una subclase de Object y aún podría asignar cualquier objeto a la matriz). Por supuesto, puedo ver que hay una pequeña diferencia entre las dos, pero esto El código es defectuoso, ya sea que use genéricos o no.

¿Qué quieren decir con la contaminación del montón (me hace pensar en el uso de la memoria, pero el único problema del que hablan es el tipo de inseguridad) y en qué se diferencia de cualquier tipo de violación que utiliza la covarianza de los arreglos?


Antes de los genéricos, no había absolutamente ninguna posibilidad de que el tipo de tiempo de ejecución de un objeto fuera inconsistente con su tipo estático. Esto es obviamente una propiedad muy deseable.

Podemos convertir un objeto en un tipo de tiempo de ejecución incorrecto, pero la conversión fallaría inmediatamente, en el sitio exacto de lanzamiento; el error se detiene allí.

Object obj = "string"; ((Integer)obj).intValue(); // we are not gonna get an Integer object

Con la introducción de genéricos, junto con el borrado de tipo (la raíz de todos los males), ahora es posible que un método devuelva String en tiempo de compilación, pero devuelva Integer en tiempo de ejecución. Esto está en mal estado. Y deberíamos hacer todo lo posible para detenerlo desde la fuente. Es por eso que el compilador es tan vocal acerca de cada vista de lanzamientos no verificados.

¡Lo peor de la contaminación del montón es que el comportamiento en tiempo de ejecución no está definido! Diferente compilador / tiempo de ejecución puede ejecutar el programa de diferentes maneras. Ver case2 y case2 .


Desde el documento vinculado, creo que lo que Oracle quiere decir con "contaminación del montón" es tener valores de datos que técnicamente están permitidos por la especificación JVM, pero no están permitidos por las reglas para genéricos en el lenguaje de programación Java.

Para darle un ejemplo, digamos que definimos un contenedor de List simple como este:

class List<E> { Object[] values; int len = 0; List() { values = new Object[10]; } void add(E obj) { values[len++] = obj; } E get(int i) { return (E)values[i]; } }

Este es un ejemplo de código que es genérico y seguro:

List<String> lst = new List<String>(); lst.add("abc");

Este es un ejemplo de código que usa tipos brutos (que omiten los genéricos) pero que aún respeta la seguridad de tipos a nivel semántico, porque el valor que agregamos tiene un tipo compatible:

String x = (String)lst.values[0];

El giro: ahora, aquí está el código que funciona con tipos crudos y hace algo malo, causando "contaminación del montón":

lst.values[lst.len++] = new Integer("3");

El código anterior funciona porque la matriz es de tipo Object[] , que puede almacenar un Integer . Ahora, cuando intentamos recuperar el valor, causará una ClassCastException , en el momento de la recuperación (que es mucho después de que ocurrió la corrupción), en lugar de en el momento de agregar:

String y = lst.get(1); // ClassCastException for Integer(3) -> String

Tenga en cuenta que la ClassCastException ocurre en nuestro marco de pila actual, ni siquiera en List.get() , porque la List.get() en List.get() es una List.get() sin tiempo de ejecución debido al sistema de borrado de tipos de Java.

Básicamente, insertamos un Integer en una List<String> al omitir los genéricos. Luego, cuando intentamos get() un elemento, el objeto de la lista no cumplió su promesa de que debe devolver un String (o null ).


La diferencia entre una matriz y una Lista es que la matriz verifica sus referencias. p.ej

Object[] array = new String[1]; array[0] = new Integer(1); // fails at runtime.

sin embargo

List list = new ArrayList<String>(); list.add(new Integer(1)); // doesn''t fail.


Son diferentes porque ClassCastException y ArrayStoreException son diferentes.

Las reglas de verificación de tipos de tiempo de compilación de los genéricos deben garantizar que sea imposible obtener una ClassCastException en un lugar donde no se colocó una conversión explícita, a menos que su código (o algún código al que haya llamado o llamado) haya hecho algo inseguro en el momento de compilación. en cuyo caso debería (o cualquier código que haya hecho la cosa insegura) recibir una advertencia de compilación al respecto.

ArrayStoreException , por otro lado, es una parte normal de cómo funcionan los arreglos en Java, y es anterior a los genéricos. No es posible que la verificación de tipos en tiempo de compilación impida ArrayStoreException debido a la forma en que el sistema de tipos para arreglos está diseñado en Java.


Tienes razón en que el problema común (y fundamental) está relacionado con la covarianza de los arreglos. Pero de los dos ejemplos que dio, el primero es más peligroso, porque puede modificar sus estructuras de datos y ponerlas en un estado que se romperá mucho más adelante.

Considere si su primer ejemplo no ha activado la excepción ClassCastException:

public static void faultyMethod(List<String>... l) { Object[] objectArray = l; // Valid objectArray[0] = Arrays.asList(42); // Also valid }

Y así es como alguien lo usa:

List<String> firstList = Arrays.asList("hello", "world"); List<String> secondList = Arrays.asList("hello", "dolly"); faultyMethod(firstList, secondList); return secondList.isEmpty() ? firstList : secondList;

Así que ahora tenemos una List<String> que en realidad contiene un Integer , y está flotando, de forma segura. En algún momento posterior, posiblemente mucho más tarde, y si se serializa, posiblemente mucho más tarde y en una JVM diferente, alguien finalmente ejecuta String s = theList.get(0) . Este fallo está tan lejos de lo que lo causó que podría ser muy difícil rastrearlo.

Tenga en cuenta que el seguimiento de pila de la ClassCastException no nos dice dónde ocurrió realmente el error; Simplemente nos dice quién lo desencadenó. En otras palabras, no nos da mucha información sobre cómo solucionar el error; y eso es lo que lo hace más importante que una ArrayStoreException.