¿Por qué comstack este código genérico en java 8?

Me encontré con un código que me hace preguntarme por qué comstack con éxito:

public class Main { public static void main(String[] args) { String s = newList(); // why does this line compile? System.out.println(s); } private static <T extends List> T newList() { return (T) new ArrayList(); } } 

Lo que es interesante es que si newList la firma del método newList con <T extends ArrayList> ya no funciona.

Actualizar después de comentarios y respuestas: si muevo el tipo genérico del método a la clase, el código ya no se comstack:

 public class SomeClass<T extends List> { public void main(String[] args) { String s = newList(); // this doesn't compile anymore System.out.println(s); } private T newList() { return (T) new ArrayList(); } } 

Si declara un parámetro de tipo en un método, está permitiendo que la persona que llama elija un tipo real para él, siempre que ese tipo real cumpla con las restricciones. Ese tipo no tiene que ser un tipo concreto real, podría ser un tipo abstracto, una variable de tipo o un tipo de intersección, en otras palabras más coloquiales, un tipo hipotético. Entonces, como dijo Mureinik , podría haber un tipo extendiendo String e implementando List . No podemos especificar manualmente un tipo de intersección para la invocación, pero podemos usar una variable de tipo para demostrar la lógica:

 public class Main { public static > void main(String[] args) { String s = Main.newList(); System.out.println(s); } private static > T newList() { return (T) new ArrayList(); } } 

Por supuesto, newList() no puede cumplir la expectativa de devolver ese tipo, pero ese es el problema de la definición (o implementación) de este método. Debería recibir una advertencia de “desactivada” cuando ArrayList a T La única implementación correcta posible sería devolver el null aquí, lo que hace que el método sea bastante inútil.

El punto, para repetir la statement inicial, es que la persona que llama de un método genérico elige los tipos reales para los parámetros de tipo. Por el contrario, cuando declaras una clase genérica como con

 public class SomeClass> { public void main(String[] args) { String s = newList(); // this doesn't compile anymore System.out.println(s); } private T newList() { return (T) new ArrayList(); } } 

el parámetro tipo es parte del contrato de la clase, por lo que quien crea una instancia elegirá los tipos reales para esa instancia. El método main instancia es parte de esa clase y tiene que obedecer ese contrato. No puedes elegir la T que quieres; se ha establecido el tipo real para T y en Java, por lo general ni siquiera se puede saber qué es T

El punto clave de la progtwigción genérica es escribir código que funcione independientemente de los tipos reales que se hayan elegido para los parámetros de tipo.

Pero tenga en cuenta que puede crear otra instancia independiente con el tipo que desee e invocar el método, por ejemplo

 public class SomeClass> { public > void main(String[] args) { String s = new SomeClass().newList(); System.out.println(s); } private T newList() { return (T) new ArrayList(); } } 

Aquí, el creador de la nueva instancia selecciona los tipos reales para esa instancia. Como se dijo, ese tipo real no necesita ser un tipo concreto.

Supongo que esto es porque List es una interfaz. Si ignoramos el hecho de que String es final por un segundo, podría, en teoría, tener una clase que extends String (lo que significa que podría asignarlo a s ) pero implements List (lo que significa que podría ser devuelto por newList() ) Una vez que cambia el tipo de devolución de una interfaz ( T extends List ) a una clase concreta ( T extends ArrayList ), el comstackdor puede deducir que no se pueden asignar entre sí y produce un error.

Esto, por supuesto, se rompe ya que String es, de hecho, final , y podríamos esperar que el comstackdor tenga esto en cuenta. En mi humilde opinión, es un error, aunque debo admitir que no soy un experto en comstackdores y que podría haber una buena razón para ignorar el modificador final en este momento.

No sé por qué esta comstackción. Por otro lado, puedo explicar cómo puede aprovechar al máximo las comprobaciones en tiempo de comstackción.

Entonces, newList() es un método genérico, tiene un parámetro de tipo. Si especifica este parámetro, el comstackdor lo comprobará por usted:

No se puede comstackr:

 String s = Main.newList(); // this doesn't compile anymore System.out.println(s); 

Pasa el paso de comstackción:

 List l = Main.>newList(); // this compiles and works well System.out.println(l); 

Especificando el parámetro tipo

Los parámetros de tipo solo proporcionan verificación en tiempo de comstackción. Esto es por diseño, java usa borrado de tipo para tipos generics. Para hacer que el comstackdor trabaje para usted, debe especificar esos tipos en el código.

Escriba parámetro en creación de instancia

El caso más común es especificar los patrones para una instancia de objeto. Es decir, para las listas:

 List list = new ArrayList<>(); 

Aquí podemos ver que List especifica el tipo para los elementos de la lista. Por otro lado, la nueva ArrayList<>() no. Utiliza el operador de diamantes en su lugar. Es decir, el comstackdor de Java infiere el tipo basado en la statement.

Parámetro de tipo implícito en la invocación del método

Cuando invocas un método estático, entonces debes especificar el tipo de otra manera. Algunas veces puedes especificarlo como un parámetro:

 public static  T max(T n1, T n2) { if (n1.doubleValue() < n2.doubleValue()) { return n2; } return n1; } 

El puede usarlo así:

 int max = max(3, 4); // implicit param type: Integer 

O así:

 double max2 = max(3.0, 4.0); // implicit param type: Double 

Parámetros de tipo explícitos en la invocación del método:

Digamos, por ejemplo, que así puede crear una lista vacía segura de tipos:

 List noIntegers = Collections.emptyList(); 

El parámetro de tipo se pasa al método emptyList() . La única restricción es que también debe especificar la clase. Es decir, no puedes hacer esto:

 import static java.util.Collections.emptyList; ... List noIntegers = emptyList(); // this won't compile 

Token de tipo de tiempo de ejecución

Si ninguno de estos trucos puede ayudarlo, entonces puede especificar un token de tipo de tiempo de ejecución . Es decir, proporciona una clase como parámetro. Un ejemplo común es EnumMap :

 private static enum Letters {A, B, C}; // dummy enum ... public static void main(String[] args) { Map map = new EnumMap<>(Letters.class); } 
    Intereting Posts