Métodos generics de Java en clases de generics

Si crea una clase genérica en Java (la clase tiene parámetros de tipo genérico), ¿puede usar métodos generics (el método toma los parámetros de tipo genérico)?

Considere el siguiente ejemplo:

public class MyClass { public  K doSomething(K k){ return k; } } public class MyGenericClass { public  K doSomething(K k){ return k; } public  List makeSingletonList(K k){ return Collections.singletonList(k); } } 

Como era de esperar con un método genérico, puedo llamar a doSomething(K) en instancias de MyClass con cualquier objeto:

 MyClass clazz = new MyClass(); String string = clazz.doSomething("String"); Integer integer = clazz.doSomething(1); 

Sin embargo, si trato de usar instancias de MyGenericClass sin especificar un tipo genérico, al invocar doSomething(K) devuelve un Object , independientemente de lo que se haya pasado K :

 MyGenericClass untyped = new MyGenericClass(); // this doesn't compile - "Incompatible types. Required: String, Found: Object" String string = untyped.doSomething("String"); 

Curiosamente, se comstackrá si el tipo de devolución es una clase genérica, por ejemplo, List (en realidad, esto se puede explicar, ver respuesta a continuación):

 MyGenericClass untyped = new MyGenericClass(); List list = untyped.makeSingletonList("String"); // this compiles 

Además, se comstackrá si se escribe la clase genérica, aunque solo sea con comodines:

 MyGenericClass wildcard = new MyGenericClass(); String string = wildcard.doSomething("String"); // this compiles 
  • ¿Hay una buena razón por la que no debería funcionar llamar a un método genérico en una clase genérica sin tipo?

  • ¿Existe algún truco inteligente relacionado con las clases genéricas y los métodos generics que me falta?

EDITAR:

Para aclarar, esperaría que una clase genérica sin tipo o sin formato no respetara los parámetros de tipo de la clase genérica (porque no se han proporcionado). Sin embargo, no está claro para mí por qué una clase genérica sin tipo o sin formato significaría que los métodos generics no se cumplen.

Resulta que este tema ya ha sido planteado en SO, cf esta pregunta . Las respuestas a esto explican que cuando una clase está sin tipo / en su forma cruda, todos los generics se eliminan de la clase, incluida la tipificación de métodos generics.

Sin embargo, no hay realmente una explicación de por qué este es el caso. Permítanme aclarar mi pregunta:

  • ¿Por qué Java elimina el método genérico al escribir en clases genéricas sin tipo o sin formato? ¿Hay una buena razón para esto o fue solo un descuido?

EDITAR – discusión de JLS:

Se ha sugerido (en respuesta a la pregunta SO anterior y a esta pregunta) que esto se trata en JLS 4.8 , que establece:

El tipo de un constructor (§8.8), el método de instancia (§8.4, §9.4) o el campo no estático (§8.3) M de un tipo C sin procesar que no se hereda de sus superclases o superinterfaces es el tipo bruto que le corresponde a la eliminación de su tipo en la statement genérica correspondiente a C.

Me queda claro cómo esto se relaciona con una clase sin tipo: los tipos generics de clase se reemplazan con los tipos de borrado. Si los generics de clase están vinculados, entonces el tipo de borrado corresponde a esos límites. Si no están vinculados, entonces el tipo de borrado es Objeto, por ejemplo

 // unbound class types public class MyGenericClass { public T doSomething(T t) { return t; } } MyGenericClass untyped = new MyGenericClass(); Object t = untyped.doSomething("String"); // bound class types public class MyBoundedGenericClass { public T doSomething(T t) { return t; } } MyBoundedGenericClass bounded = new MyBoundedGenericClass(); Object t1 = bounded.doSomething("String"); // does not compile Number t2 = bounded.doSomething(1); // does compile 

Si bien los métodos generics son métodos de instancia, no me queda claro que JLS 4.8 se aplique a métodos generics. El tipo de método genérico ( en el ejemplo anterior) no está sin tipo, ya que su tipo está determinado por los parámetros del método: solo la clase está sin tipo / sin formato.

‘para la compatibilidad con versiones anteriores’ parece una razón suficiente para borrar tipos de tipos generics de clase; es necesario, por ejemplo, para permitirle devolver una Lista sin tipo y pasarla a algún código heredado. La extensión de esto a métodos generics parece una subventaja complicada.

El fragmento de JLS de 4.8 (que cita) cubre constructores, métodos de instancia y campos de miembros: los métodos generics son solo un caso particular de los métodos de instancia en general. Por lo tanto, parece que su caso está cubierto por este fragmento.

Adaptación de JLS 4.8 a este caso específico:

El tipo de método genérico es el tipo sin procesar que corresponde al borrado de su tipo en la statement genérica correspondiente a C.

(aquí el ‘tipo’ del método incluiría todos los tipos de parámetros y de retorno). Si interpreta “borrado” como “borrado de todos los generics”, parece que coincide con el comportamiento observado, aunque no es muy intuitivo o útil. Casi parece una consistencia demasiado entusiasta, borrar todos los generics, en lugar de solo los parámetros generics de la clase (aunque ¿quién soy? ¿Adivino a los diseñadores?).

Quizás haya problemas donde los parámetros generics de clase interactúen con los parámetros generics del método; en su código son completamente independientes, pero podría imaginarse otros casos en los que se asignan / mezclan. Creo que vale la pena señalar que no se recomienda el uso de tipos crudos, según el JLS:

El uso de tipos crudos solo se permite como concesión a la compatibilidad del código heredado. Se desaconseja encarecidamente el uso de tipos crudos en el código escrito después de la introducción de genericidad en el lenguaje de progtwigción Java. Es posible que las versiones futuras del lenguaje de progtwigción Java no permitan el uso de tipos crudos

Parte del pensamiento de los desarrolladores de Java es evidente aquí:

http://bugs.sun.com/view_bug.do?bug_id=6400189

(error + solución que muestra que el tipo de devolución de un método se trata como parte del tipo de método para los fines de este tipo de borrado)

También existe esta solicitud , en la que alguien parece solicitar el comportamiento que usted describe, solo borra los parámetros generics de la clase, no otros generics, pero fue rechazado con este razonamiento:

La solicitud es modificar el borrado de tipo para que en la statement de tipo Foo , el borrado solo elimine T de los tipos parametrizados. Entonces, sucede que dentro de la statement de Map , Set> borra a Set .

Pero si Map tuviera un método que tomara el tipo Map , su borrado sería Map . El borrado de tipos para cambiar la cantidad de parámetros de tipo es horrible, especialmente para la resolución de métodos en tiempo de comstackción. Absolutamente no vamos a aceptar esta solicitud.

Es demasiado esperar que se puedan usar tipos crudos ( Map ) mientras se obtienen algunos de los tipos de seguridad de los generics ( Set ).

Con Java Generics, si usa la forma cruda de una clase genérica, todos los generics de la clase, incluso los métodos generics no relacionados como los métodos makeSingletonList y makeSingletonList , se convierten en raw. La razón, según tengo entendido, es la de ofrecer compatibilidad con versiones anteriores del código Java escrito pre-generics.

Si no hay uso para su parámetro de tipo genérico T , simplemente elimínelo de MyGenericClass , dejando sus métodos generics con K De lo contrario, tendrá que vivir con el hecho de que el parámetro de tipo de clase T debe asignarse a su clase para usar generics en cualquier otra cosa de la clase.

He encontrado una razón para descartar completamente los generics (sin embargo, no es tan bueno). La razón es que: los generics pueden estar limitados. Considera esta clase:

 public static class MyGenericClass { public  K doSomething(K k){ return k; } public  List makeSingletonList(K k){ return Collections.singletonList(k); } } 

El comstackdor debe descartar los generics en doSomething , cuando utiliza la clase sin generics. Y creo que todos los generics se descartan para ser consistentes con este comportamiento.

makeSingletonList comstack porque Java realiza un lanzamiento no seleccionado de List to List (sin embargo, el comstackdor muestra una advertencia).

Esto no responde a la pregunta fundamental, pero aborda el problema de por qué makeSingletonList(...) comstack mientras que doSomething(...) no:

En una implementación sin tipo de MyGenericClass :

 MyGenericClass untyped = new MyGenericClass(); List list = untyped.makeSingletonList("String"); 

…es equivalente a:

 MyGenericClass untyped = new MyGenericClass(); List list = untyped.makeSingletonList("String"); List typedList = list; 

Esto se comstackrá (con varias advertencias), pero luego estará abierto a errores de tiempo de ejecución.

De hecho, esto también es análogo a:

 MyGenericClass untyped = new MyGenericClass(); List list = untyped.makeSingletonList("String"); // this compiles!!! Integer a = list.get(0); 

… que comstack pero arrojará una ClassCastException tiempo de ejecución cuando intente obtener el valor String y convertirlo en un Integer .

La razón de esto es la compatibilidad con el código pre-genérico. El código pre-genérico no usaba argumentos generics, sino que usaba lo que hoy parece ser un tipo crudo. El código pre-genérico usaría referencias a Object lugar de referencias usando el tipo genérico, y los tipos crudos usarían el Object para todos los argumentos generics, por lo que el código era verdaderamente retrocompatible.

Como ejemplo, considere este código:

 List list = new ArrayList(); 

Este es un código pregenérico, y una vez que se introdujeron los generics, esto se interpretó como un tipo genérico sin procesar equivalente a:

 List list = new ArrayList<>(); 

Porque el ? no tiene una palabra clave extends o super después de ella, se transforma en esto:

 List list = new ArrayList<>(); 

La versión de List que se usaba antes de los generics usaba un Object para referirse a un elemento de lista, y esta versión también usa un Object para referirse a un elemento de lista, por lo que se conserva la compatibilidad con versiones anteriores.

De mi comentario sobre la respuesta de MAnyKeys:

Creo que el borrado de tipo completo tiene sentido combinado con la compatibilidad hacia atrás mencionada. Cuando el objeto se crea sin argumentos de tipo genérico, ¿puede (o debería ser?) Asumir que el uso del objeto tiene lugar en un código no genérico.

Considere este código heredado:

 public class MyGenericClass { public Object doSomething(Object k){ return k; } } 

llamado así:

 MyGenericClass foo = new MyGenricClass(); NotKType notKType = foo.doSomething(new NotKType()); 

Ahora la clase y su método se hacen generics:

 public class MyGenericClass { public  K doSomething(K k){ return k; } } 

Ahora el código de llamada anterior no se comstackrá más ya que NotKType no es una suposición de KType . Para evitar esto, los tipos generics se reemplazan con Object . Aunque hay casos en los que no haría ninguna diferencia (como su ejemplo), al menos es muy complejo para el comstackdor analizar cuándo. Podría ser incluso imposible.

Sé que este szenario parece un poco construido, pero estoy seguro de que sucede de vez en cuando.

    Intereting Posts