reificacion lukacs cosificacion conocimiento adorno java generics reification

lukacs - ¿Por qué debería importarme que Java no tenga genéricos reificados?



reificacion lukacs (13)

Algo que todas las respuestas aquí se han perdido y que constantemente me causa dolor de cabeza es que, dado que los tipos se borran, no se puede heredar una interfaz genérica dos veces. Esto puede ser un problema cuando quieres hacer interfaces de grano fino.

public interface Service<KEY,VALUE> { VALUE get(KEY key); } public class PersonService implements Service<Long, Person>, Service<String, Person> //Can not do!!

Esto surgió como una pregunta que hice en una entrevista recientemente como algo que el candidato deseaba agregar al lenguaje Java. Comúnmente se identifica como un dolor que Java no tiene genéricos reificados , pero cuando se lo empuja, el candidato no puede decirme realmente el tipo de cosas que podría haber logrado si estuvieran allí.

Obviamente, debido a que los tipos sin procesar son permitidos en Java (y las comprobaciones inseguras), es posible subvertir los genéricos y terminar con una List<Integer> que (por ejemplo) realmente contiene String . Esto claramente podría ser imposible si se reificara la información del tipo; ¡pero debe haber más que esto !

¿Podrían las personas publicar ejemplos de cosas que realmente querrían hacer , estaban disponibles los genéricos reificados? Quiero decir, obviamente podrías obtener el tipo de una List en tiempo de ejecución, pero ¿qué harías con ella?

public <T> void foo(List<T> l) { if (l.getGenericType() == Integer.class) { //yeah baby! err, what now?

EDITAR : Una actualización rápida de esto, ya que las respuestas parecen estar principalmente preocupadas por la necesidad de pasar una Class como parámetro (por ejemplo EnumSet.noneOf(TimeUnit.class) ). Estaba buscando algo más en la línea de donde esto simplemente no es posible . Por ejemplo:

List<?> l1 = api.gimmeAList(); List<?> l2 = api.gimmeAnotherList(); if (l1.getGenericType().isAssignableFrom(l2.getGenericType())) { l1.addAll(l2); //why on earth would I be doing this anyway?


Aquí hay uno que me atrapó hoy: sin reificación, si escribes un método que acepte una lista varargs de artículos genéricos ... las personas que llaman pueden pensar que son inseguras en cuanto a tipo de letra, pero que pasan accidentalmente en cualquier edad y explotan tu método.

Parece poco probable que eso suceda? ... Claro, hasta ... usa Class como su tipo de datos. En este punto, su interlocutor le enviará encantados muchos objetos Clase, pero un simple error tipográfico le enviará objetos Clase que no se adhieren a T, y desastres.

(NB: Puede que haya cometido un error aquí, pero buscando en Google "genéricos varargs", lo anterior parece ser justo lo que esperarías. Lo que hace que esto sea un problema práctico es el uso de Class, creo - los llamadores parecen tener menos cuidado :()

Por ejemplo, estoy usando un paradigma que usa objetos de Clase como clave en los mapas (es más complejo que un simple mapa, pero conceptualmente eso es lo que está sucediendo).

por ejemplo, esto funciona muy bien en Java Generics (ejemplo trivial):

public <T extends Component> Set<UUID> getEntitiesPossessingComponent( Class<T> componentType) { // find the entities that are mapped (somehow) from that class. Very type-safe }

por ejemplo, sin reificación en Java Generics, este acepta CUALQUIER objeto de "Clase". Y es solo una pequeña extensión del código anterior:

public <T extends Component> Set<UUID> getEntitiesPossessingComponents( Class<T>... componentType ) { // find the entities that are mapped (somehow) to ALL of those classes }

Los métodos anteriores tienen que escribirse miles de veces en un proyecto individual, por lo que la posibilidad de error humano se vuelve alta. Errores de depuración está demostrando "no es divertido". Actualmente estoy tratando de encontrar una alternativa, pero no tengo mucha esperanza.


De las pocas veces que me encontré con esta "necesidad", finalmente se reduce a esta construcción:

public class Foo<T> { private T t; public Foo() { this.t = new T(); // Help? } }

Esto funciona en C # suponiendo que T tiene un constructor predeterminado . Incluso puede obtener el tipo de tiempo de ejecución por typeof(T) y obtener los constructores por Type.GetConstructor() .

La solución común de Java sería pasar la Class<T> como argumento.

public class Foo<T> { private T t; public Foo(Class<T> cls) throws Exception { this.t = cls.newInstance(); } }

(No necesariamente se debe pasar como argumento de constructor, ya que un argumento de método también es bueno, lo anterior es solo un ejemplo, también se omite el try-catch para abreviar)

Para todas las demás construcciones de tipo genérico, el tipo real se puede resolver fácilmente con un poco de ayuda de reflexión. Las siguientes preguntas y respuestas ilustran los casos de uso y las posibilidades:


Esta es una vieja pregunta, hay un montón de respuestas, pero creo que las respuestas existentes están fuera de lugar.

"reificado" solo significa real y usualmente solo significa lo opuesto a borrado de tipo.

El gran problema relacionado con Java Generics:

  • Este horrible requisito de boxeo y desconexión entre primitivos y tipos de referencia. Esto no está directamente relacionado con la reificación o el borrado de tipo. C # / Scala corrige esto.
  • Sin auto tipos. JavaFX 8 tuvo que eliminar "constructores" por este motivo. Absolutamente nada que ver con el borrado de tipos. Scala soluciona esto, no está seguro acerca de C #.
  • Sin varianza del tipo de declaración lateral. C # 4.0 / Scala tiene esto. Absolutamente nada que ver con el borrado de tipos.
  • No se puede sobrecargar el void method(List<A> l) y el method(List<B> l) . Esto se debe al borrado de tipo, pero es extremadamente insignificante.
  • No admite la reflexión del tipo de tiempo de ejecución. Este es el corazón del borrado de tipos. Si le gustan los compiladores súper avanzados que verifican y prueban la lógica de su programa en el momento de la compilación, debe usar la reflexión lo menos posible y este tipo de borrado de tipo no debería molestarlo. Si te gusta la programación de tipo más desigual, dinámica y dinámica, y no te importa tanto que un compilador demuestre que tu lógica es lo más correcta posible, entonces quieres una mejor reflexión y corregir el borrado de tipos es importante.

La serialización sería más sencilla con la reificación. Lo que queremos es

deserialize(thingy, List<Integer>.class);

Lo que tenemos que hacer es

deserialize(thing, new TypeReference<List<Integer>>(){});

se ve feo y funciona de manera funky.

También hay casos en los que sería realmente útil decir algo como

public <T> void doThings(List<T> thingy) { if (T instanceof Q) doCrazyness(); }

Estas cosas no muerden a menudo, pero muerden cuando ocurren.


Las matrices probablemente jugarían mucho mejor con los genéricos si fueran reificados.


Lo que más comúnmente me pica es la incapacidad de aprovechar el envío múltiple en múltiples tipos genéricos. Lo siguiente no es posible y hay muchos casos en los que sería la mejor solución:

public void my_method(List<String> input) { ... } public void my_method(List<Integer> input) { ... }


Mi exposición a Java Geneircs es bastante limitada, y aparte de los puntos que ya han mencionado otras respuestas, hay un escenario explicado en el libro Java Generics and Collections , de Maurice Naftalin y Philip Walder, donde los genéricos reificados son útiles.

Como los tipos no son reificables, no es posible tener excepciones parametrizadas.

Por ejemplo, la declaración del siguiente formulario no es válida.

class ParametericException<T> extends Exception // compile error

Esto se debe a que la cláusula de captura verifica si la excepción lanzada coincide con un tipo determinado. Esta comprobación es igual a la comprobación realizada por la prueba de instancia y dado que el tipo no es reificable, la forma de declaración anterior no es válida.

Si el código anterior era válido, entonces la gestión de excepciones en la forma siguiente habría sido posible:

try { throw new ParametericException<Integer>(42); } catch (ParametericException<Integer> e) { // compile error ... }

El libro también menciona que si los genéricos de Java se definen de forma similar a como se definen las plantillas de C ++ (expansión), esto puede llevar a una implementación más eficiente, ya que ofrece más oportunidades para la optimización. Pero no ofrece ninguna explicación más que esto, por lo que cualquier explicación (punteros) de la gente conocedora sería útil.


No es que logren algo extraordinario. Simplemente será más simple de entender. La borradura de tipo parece ser un momento difícil para los principiantes y, en última instancia, requiere que uno comprenda cómo funciona el compilador.

Mi opinión es que los genéricos son simplemente un extra que ahorra mucho casting redundante.


Podrías crear matrices genéricas en tu código.

public <T> static void DoStuff() { T[] myArray = new T[42]; // No can do }


Tengo un contenedor que presenta un conjunto de resultados jdbc como un iterador, (esto significa que puedo probar las operaciones originadas en la base de datos mucho más fácilmente a través de la inyección de dependencia).

La API se parece a Iterator<T> donde T es un tipo que se puede construir utilizando solo cadenas en el constructor. El iterador luego mira las cadenas que se devuelven desde la consulta sql y luego intenta emparejarlo con un constructor de tipo T.

En la forma actual en que se implementan los genéricos, también tengo que pasar la clase de los objetos que crearé a partir de mi conjunto de resultados. Si lo entiendo correctamente, si se reificaron los genéricos, podría simplemente llamar a T.getClass () para obtener sus constructores, y luego no tener que lanzar el resultado de Class.newInstance (), que sería mucho más limpio.

Básicamente, creo que hace que escribir API (en lugar de simplemente escribir una aplicación) sea más fácil, porque se puede inferir mucho más de los objetos y, por lo tanto, será necesaria menos configuración ... No aprecié las implicaciones de las anotaciones hasta que los vi usar en cosas como spring o xstream en lugar de resmas de config.


Una cosa buena sería evitar el boxeo para los tipos primitivos (de valor). Esto está relacionado de alguna manera con la serie de quejas que otros han planteado, y en los casos en que el uso de la memoria está restringido, en realidad podría marcar una diferencia significativa.

También existen varios tipos de problemas al escribir un marco donde es importante poder reflejar el tipo parametrizado. Por supuesto, esto puede solucionarse pasando un objeto de clase en tiempo de ejecución, pero esto oscurece la API y supone una carga adicional para el usuario del marco.


Tipo de seguridad viene a la mente. Downcasting a un tipo parametrizado siempre será inseguro sin genéricos reificados:

List<String> myFriends = new ArrayList(); myFriends.add("Alice"); getSession().put("friends", myFriends); // later, elsewhere List<Friend> myFriends = (List<Friend>) getSession().get("friends"); myFriends.add(new Friend("Bob")); // works like a charm! // and so... List<String> myFriends = (List<String>) getSession().get("friends"); for (String friend : myFriends) print(friend); // ClassCastException, wtf!?

Además, las abstracciones perderían menos , al menos las que podrían estar interesadas en la información de tiempo de ejecución sobre sus parámetros de tipo. Hoy, si necesita algún tipo de información de tiempo de ejecución sobre el tipo de uno de los parámetros genéricos, también debe pasar su Class . De esta forma, su interfaz externa depende de su implementación (ya sea que use RTTI sobre sus parámetros o no).