java generics foreach

java - Omitir<?> Rompe de manera intuitiva este código



generics foreach (1)

En primer lugar, el cuerpo del método de su ejemplo de código se puede simplificar a:

public void traverse() { MyEntity myEntity = null; for (String label : myEntity.getLabels()) { // <-- Offending Line /* ... */ } }

¿Por qué está sucediendo eso? Porque cuando declaras la variable myEntity (no importa dónde -en for-loop o como en mi ejemplo) como MyEntity myEntity , la declaras como tipo sin procesar , lo que elimina el tipo genérico del tipo de retorno del método getLabels también: así que se vuelve como List getLabels(); , donde obviamente se espera el tipo de Object para la construcción for-loop.

En el mismo tiempo Iterator<String> iterator = myEntity.getLabels().iterator(); funciona bien, porque está especificando el tipo aquí explícitamente: Iterator<String> .

Un ejemplo muy similar se da en JLS 4.8 "Tipos crudos" que explica por qué sucede:

... Los miembros de tipo heredado que dependen de variables de tipo se heredarán como tipos sin procesar como consecuencia de la regla de que los supertipos de un tipo sin procesar se borran ...

Otra implicación de las reglas anteriores es que una clase interna genérica de un tipo sin procesar solo puede usarse en sí misma como un tipo sin procesar:

class Outer<T>{ class Inner<S> { S s; } }

No es posible acceder a Inner como un tipo parcialmente sin procesar (un tipo "raro"):

Outer.Inner<Double> x = null; // illegal

UPD-2 : cuando recibí preguntas sobre Iterator<String> iterator = myEntity.getLabels().iterator(); , ¿por qué está bien hacerlo, mientras que el primer ejemplo no funciona?

Personalmente estoy de acuerdo en que parece confuso. Pero esas son las reglas. Este caso también está cubierto en el mismo párrafo JLS con este ejemplo:

class Cell<E> { E value; Cell(E v) { value = v; } E get() { return value; } void set(E v) { value = v; } public static void main(String[] args) { Cell x = new Cell<String>("abc"); System.out.println(x.value); // OK, has type Object System.out.println(x.get()); // OK, has type Object x.set("def"); // unchecked warning } }

Explicación más cuidadosa sobre por qué Iterator<String> iterator = myEntity.getLabels().iterator(); el trabajo de JLS se basa en esta regla:

Es decir, las reglas de subtipificación (§4.10.2) del lenguaje de programación Java permiten que a una variable de un tipo sin formato se le asigne un valor de cualquiera de las instancias parametrizadas del tipo.

De la misma manera siempre puedes escribir código bien compilado como:

List<String> labels = myEntity.getLabels(); for (String label : labels) { // <-- OK, no error here /* ... */ }

Creé un MWE donde el cambio de una sola línea al agregar <?> Resuelve un error de compilación.

El siguiente código no se compila:

import java.util.List; public class MainClass { public void traverse() { List<MyEntity> list = null /* ... */; for (MyEntity myEntity : list) { for (String label : myEntity.getLabels()) { // <-- Offending Line /* ... */ } } } interface MyEntity<T> { T get(); List<String> getLabels(); } }

El error del compilador es:

Error:(9, 51) java: incompatible types: java.lang.Object cannot be converted to java.lang.String

Al cambiar la definición en la línea ofensiva de MyEntity myEntity a MyEntity<?> myEntity resuelve el problema. Me pregunto por qué el tipo de devolución de esto para cada uno se trata como un Object y no como una String , a menos que agregue el comodín a la clase principal. Tenga en cuenta que getLabels() sí mismo no contiene genéricos.

De acuerdo con el Capítulo 14.14.2. de la Especificación del lenguaje Java , un for-each se compila en un bucle utilizando un iterador. Curiosamente, la expansión de for-each a dicho iterador funciona de forma manual:

Iterator<String> iterator = myEntity.getLabels().iterator(); while (iterator.hasNext()) { String label = iterator.next(); /* ... */ }

¿Alguien puede explicar por qué?