Ocasionalmente he escuchado que con los genéricos, Java no lo hizo bien. (referencia más cercana, aquí )
Disculpe mi inexperiencia, pero ¿qué los habría mejorado?
Ocasionalmente he escuchado que con los genéricos, Java no lo hizo bien. (referencia más cercana, aquí )
Disculpe mi inexperiencia, pero ¿qué los habría mejorado?
Respuestas:
Malo:
List<byte>
realmente está respaldado por, byte[]
por ejemplo, y no se requiere boxing)Bueno:
El mayor problema es que los genéricos de Java son una cosa solo en tiempo de compilación, y puede subvertirlos en tiempo de ejecución. C # es elogiado porque realiza más comprobaciones en tiempo de ejecución. Hay una muy buena discusión en esta publicación y se vincula a otras discusiones.
Class
objetos.
El principal problema es que Java en realidad no tiene genéricos en tiempo de ejecución. Es una función de tiempo de compilación.
Cuando creas una clase genérica en Java, usan un método llamado "Borrado de tipos" para eliminar todos los tipos genéricos de la clase y, esencialmente, reemplazarlos con Object. La versión más alta de los genéricos es que el compilador simplemente inserta conversiones en el tipo genérico especificado siempre que aparece en el cuerpo del método.
Esto tiene muchos inconvenientes. Uno de los más importantes, en mi humilde opinión, es que no se puede utilizar la reflexión para inspeccionar un tipo genérico. Los tipos no son realmente genéricos en el código de bytes y, por lo tanto, no se pueden inspeccionar como genéricos.
Gran descripción general de las diferencias aquí: http://www.jprl.com/Blog/archive/development/2007/Aug-31.html
(1) conduce a un comportamiento muy extraño. El mejor ejemplo en el que puedo pensar es. Asumir:
public class MyClass<T> {
T getStuff() { ... }
List<String> getOtherStuff() { ... }
}
luego declare dos variables:
MyClass<T> m1 = ...
MyClass m2 = ...
Ahora llame getOtherStuff()
:
List<String> list1 = m1.getOtherStuff();
List<String> list2 = m2.getOtherStuff();
El segundo tiene su argumento de tipo genérico eliminado por el compilador porque es un tipo sin formato (lo que significa que el tipo parametrizado no se proporciona) aunque no tiene nada que ver con el tipo parametrizado.
También mencionaré mi declaración favorita del JDK:
public class Enum<T extends Enum<T>>
Aparte del comodín (que es una mezcla), creo que los genéricos .Net son mejores.
public class Redundancy<R extends Redundancy<R>>
;)
The expression of type List needs unchecked conversion to conform to List<String>
Enum<T extends Enum<T>>
puede parecer extraño / redundante al principio, pero en realidad es bastante interesante, al menos dentro de las limitaciones de Java / sus genéricos. Las enumeraciones tienen un values()
método estático que proporciona una matriz de sus elementos escritos como enumeración, no Enum
, y ese tipo está determinado por el parámetro genérico, lo que significa que desea Enum<T>
. Por supuesto, esa escritura solo tiene sentido en el contexto de un tipo enumerado, y todas las enumeraciones son subclases de Enum
, por lo tanto, lo desea Enum<T extends Enum>
. Sin embargo, a Java no le gusta mezclar tipos sin procesar con genéricos, Enum<T extends Enum<T>>
por lo tanto, por coherencia.
Voy a lanzar una opinión muy controvertida. Los genéricos complican el lenguaje y complican el código. Por ejemplo, digamos que tengo un mapa que asigna una cadena a una lista de cadenas. En los viejos tiempos, podría declarar esto simplemente como
Map someMap;
Ahora, tengo que declararlo como
Map<String, List<String>> someMap;
Y cada vez que lo paso a algún método, tengo que repetir esa gran declaración de nuevo. En mi opinión, toda esa escritura extra distrae al desarrollador y lo saca de "la zona". Además, cuando el código está lleno de mucho contenido, a veces es difícil volver a él más tarde y examinar rápidamente todo el contenido para encontrar la lógica importante.
Java ya tiene una mala reputación por ser uno de los lenguajes más detallados de uso común, y los genéricos se suman a ese problema.
¿Y qué compras realmente con toda esa verbosidad extra? ¿Cuántas veces ha tenido realmente problemas en los que alguien puso un Integer en una colección que se supone que contiene cadenas, o cuando alguien intentó extraer una cadena de una colección de Integers? En mis 10 años de experiencia trabajando en la creación de aplicaciones comerciales de Java, esto nunca ha sido una gran fuente de errores. Entonces, no estoy muy seguro de lo que obtienes por la verbosidad adicional. Realmente me parece un equipaje extra burocrático.
Ahora me voy a poner realmente polémico. Lo que veo como el mayor problema con las colecciones en Java 1.4 es la necesidad de encasillar en todas partes. Veo esos encasillamientos como un cruft extra y detallado que tienen muchos de los mismos problemas que los genéricos. Entonces, por ejemplo, no puedo simplemente hacer
List someList = someMap.get("some key");
tengo que hacer
List someList = (List) someMap.get("some key");
La razón, por supuesto, es que get () devuelve un objeto que es un supertipo de List. Entonces, la asignación no se puede hacer sin un encasillado. Una vez más, piense cuánto le compra realmente esa regla. Por mi experiencia, no mucho.
Creo que Java hubiera estado mucho mejor si 1) no hubiera agregado genéricos pero 2) en su lugar hubiera permitido la conversión implícita de un supertipo a un subtipo. Deje que los lanzamientos incorrectos se detecten en tiempo de ejecución. Entonces podría haber tenido la sencillez de definir
Map someMap;
y luego haciendo
List someList = someMap.get("some key");
todo el problema se habría ido, y realmente no creo que esté introduciendo una gran fuente nueva de errores en mi código.
Otro efecto secundario de que sean de tiempo de compilación y no de tiempo de ejecución es que no puede llamar al constructor del tipo genérico. Entonces no puede usarlos para implementar una fábrica genérica ...
public class MyClass {
public T getStuff() {
return new T();
}
}
--jeffk ++
Los genéricos de Java se comprueban para verificar su exactitud en el momento de la compilación y luego se elimina toda la información de tipo (el proceso se llama borrado de tipo . Por lo tanto, genérico List<Integer>
se reducirá a su tipo sin formato , no genérico List
, que puede contener objetos de clase arbitraria.
Esto da como resultado la posibilidad de insertar objetos arbitrarios en la lista en tiempo de ejecución, y ahora es imposible saber qué tipos se usaron como parámetros genéricos. Este último a su vez resulta en
ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if(li.getClass() == lf.getClass()) // evaluates to true
System.out.println("Equal");
Desearía que esto fuera un wiki para poder agregar a otras personas ... pero ...
Problemas:
<
? Extiende MyObject >
[], pero no estoy permitido)Ignorando todo el lío de borrado de tipos, los genéricos como se especifica simplemente no funcionan.
Esto compila:
List<Integer> x = Collections.emptyList();
Pero este es un error de sintaxis:
foo(Collections.emptyList());
Donde foo se define como:
void foo(List<Integer> x) { /* method body not important */ }
Entonces, si un tipo de expresión verifica depende de si se está asignando a una variable local o un parámetro real de una llamada a un método. ¿Qué tan loco es eso?
La introducción de genéricos en Java fue una tarea difícil porque los arquitectos estaban tratando de equilibrar la funcionalidad, la facilidad de uso y la compatibilidad con versiones anteriores del código heredado. Como era de esperar, hubo que hacer concesiones.
Hay quienes también sienten que la implementación de genéricos en Java aumentó la complejidad del lenguaje a un nivel inaceptable (ver " Genéricos considerados perjudiciales " de Ken Arnold ). Las preguntas frecuentes sobre genéricos de Angelika Langer dan una idea bastante clara de lo complicadas que pueden volverse las cosas.
Java no aplica los genéricos en tiempo de ejecución, solo en tiempo de compilación.
Esto significa que puede hacer cosas interesantes como agregar los tipos incorrectos a las colecciones genéricas.
Los genéricos de Java son solo en tiempo de compilación y se compilan en código no genérico. En C #, el MSIL compilado real es genérico. Esto tiene enormes implicaciones para el rendimiento porque Java todavía se lanza durante el tiempo de ejecución. Vea aquí para más .
Si escuchas Java Posse # 279 - Entrevista con Joe Darcy y Alex Buckley , hablan sobre este tema. Eso también se vincula a una publicación de blog de Neal Gafter titulada Reified Generics for Java que dice:
Mucha gente no está satisfecha con las restricciones causadas por la forma en que se implementan los genéricos en Java. Específicamente, no están contentos de que los parámetros de tipo genérico no estén reificados: no están disponibles en tiempo de ejecución. Los genéricos se implementan mediante el borrado, en el que los parámetros de tipo genérico simplemente se eliminan en tiempo de ejecución.
Esa publicación de blog hace referencia a una entrada anterior, Puzzling Through Erasure: sección de respuestas , que enfatizó el punto sobre la compatibilidad de la migración en los requisitos.
El objetivo era proporcionar compatibilidad con versiones anteriores del código fuente y objeto, y también compatibilidad con la migración.