He visto varias veces en este sitio publicaciones que denuncian la implementación de genéricos en Java. Ahora, puedo decir honestamente que no he tenido ningún problema con su uso. Sin embargo, no he intentado hacer una clase genérica yo mismo. Entonces, ¿cuáles son sus problemas con el soporte genérico de Java?
49
Respuestas:
La implementación genérica de Java utiliza borrado de tipo . Esto significa que sus colecciones genéricas fuertemente tipadas son en realidad de tipo
Object
en tiempo de ejecución. Esto tiene algunas consideraciones de rendimiento, ya que significa que los tipos primitivos deben encuadrarse cuando se agregan a una colección genérica. Por supuesto, los beneficios de la corrección de tipo de tiempo de compilación superan la tontería general de borrado de tipo y el enfoque obsesivo en la compatibilidad con versiones anteriores.fuente
TypeLiteral
eliminaciónnew ArrayList<String>.getClass() == new ArrayList<Integer>.getClass()
template<T> T add(T v1, T v2) { return v1->add(v2); }
y allí tiene una forma verdaderamente genérica de crear una función queadd
es dos cosas, sin importar cuáles sean esas cosas, solo tienen que tener un método nombradoadd()
con un parámetro.Add
ejemplo simple que di, no hay manera sin saber de antemano a qué clases se puede aplicar, lo cual es un inconveniente.Observe que las respuestas que ya se han proporcionado se concentran en la combinación de Java-the-language, JVM y Java Class Library.
No hay nada malo con los genéricos de Java en lo que respecta al lenguaje Java. Como se describe en C # frente a los genéricos de Java , los genéricos de Java están bastante bien en el nivel de lenguaje 1 .
Lo subóptimo es que la JVM no admite genéricos directamente, lo que tiene varias consecuencias importantes:
Supongo que la distinción que hago es pedante, ya que Java-the-language se compila ubicuamente con JVM como destino y Java Class Library como biblioteca principal.
1 Con la posible excepción de los comodines, que se cree que hacen que la inferencia de tipo sea indecidible en el caso general. Esta es una diferencia importante entre C # y los genéricos de Java que no se menciona con mucha frecuencia. Gracias antimonio.
fuente
8675309
) y crear un tipo único para ese número (por ejemploZ8<Z6<Z7<Z5<Z3<Z0<Z9>>>>>>>
), que tendría miembros diferentes de cualquier otro tipo. En C ++, todos los tipos que podrían generarse a partir de cualquier entrada tendrían que producirse en tiempo de compilación.La crítica habitual es la falta de reificación. Es decir, los objetos en tiempo de ejecución no contienen información sobre sus argumentos genéricos (aunque la información todavía está presente en campos, métodos, constructores y clases e interfaces extendidas). Esto significa que podría lanzar, digamos,
ArrayList<String>
aList<File>
. El compilador le dará advertencias, pero también le avisará si toma unaArrayList<String>
asignación y la envía a unaObject
referenciaList<String>
. Lo bueno es que con los genéricos probablemente no deberías estar haciendo el casting, el rendimiento es mejor sin los datos innecesarios y, por supuesto, la compatibilidad con versiones anteriores.Algunas personas se quejan de que no puede sobrecargarse sobre la base de argumentos genéricos (
void fn(Set<String>)
yvoid fn(Set<File>)
). Necesita usar mejores nombres de métodos en su lugar. Tenga en cuenta que esta sobrecarga no requeriría reificación, ya que la sobrecarga es un problema estático en tiempo de compilación.Los tipos primitivos no funcionan con los genéricos.
Los comodines y los límites son bastante complicados. Son muy útiles Si Java prefería las interfaces de inmutabilidad y tell-don't-ask, entonces los genéricos del lado de la declaración en lugar del lado del uso serían más apropiados.
fuente
Object cs = new char[] { 'H', 'i' }; System.out.println(cs);
te imprimen tonterías. Cambie el tipo decs
achar[]
y obtendráHi
.Los genéricos de Java apestan porque no puedes hacer lo siguiente:
No necesitaría cortar todas las clases en el código anterior para que funcione como debería si los genéricos fueran en realidad parámetros genéricos de la clase.
fuente
Parser
, tendré que hacer que el código de fábrica sea aún más complicado. Mientras que "genérico" realmente significa genérico, entonces no habría necesidad de fábricas y todas las demás tonterías que se conocen con el nombre de patrones de diseño. Es una de las muchas razones por las que prefiero trabajar en lenguajes dinámicos.las advertencias del compilador para los parámetros genéricos faltantes que no sirven para nada hacen que el lenguaje sea inútilmente detallado, por ejemplo
public String getName(Class<?> klazz){ return klazz.getName();}
Los genéricos no juegan bien con las matrices
La información de tipo perdido hace que la reflexión sea un desastre de fundición y cinta adhesiva.
fuente
HashMap
lugar deHashMap<String, String>
.Creo que otras respuestas han dicho esto hasta cierto punto, pero no muy claramente. Uno de los problemas con los genéricos es perder los tipos genéricos durante el proceso de reflexión. Así por ejemplo:
Desafortunadamente, los tipos devueltos no pueden decirle que los tipos genéricos de arr son String. Es una diferencia sutil, pero es importante. Debido a que arr se crea en tiempo de ejecución, los tipos genéricos se borran en tiempo de ejecución, por lo que no puede resolverlo. Como algunos dijeron, se
ArrayList<Integer>
ve igual queArrayList<String>
desde el punto de vista de la reflexión.Es posible que esto no le importe al usuario de Generics, pero supongamos que queríamos crear un marco sofisticado que utilizara la reflexión para descubrir cosas elegantes sobre cómo el usuario declaró los tipos genéricos concretos de una instancia.
Digamos que queríamos una fábrica genérica para crear una instancia
MySpecialObject
porque ese es el tipo genérico concreto que declaramos para esta instancia. Bueno, la clase Factory no puede interrogarse para averiguar el tipo concreto declarado para esta instancia porque Java los borró.En los genéricos de .Net puede hacer esto porque en tiempo de ejecución el objeto sabe que son tipos genéricos porque el compilador lo cumplió en el binario. Con borrados, Java no puede hacer esto.
fuente
Podría decir algunas cosas buenas sobre los genéricos, pero esa no era la cuestión. Podría quejarme de que no están disponibles en tiempo de ejecución y no funcionan con matrices, pero eso se ha mencionado.
Una gran molestia psicológica: ocasionalmente me meto en situaciones en las que no se puede hacer que los genéricos funcionen. (Las matrices son el ejemplo más simple). Y no puedo entender si los genéricos no pueden hacer el trabajo o si soy simplemente estúpido. Odio eso. Algo como los genéricos debería funcionar siempre. Cada dos veces que no puedo hacer lo que quiero usando Java-the-language, sé que el problema soy yo y sé que si sigo presionando, llegaré allí eventualmente. Con los genéricos, si me vuelvo demasiado persistente, puedo perder mucho tiempo.
Pero el verdadero problema es que los genéricos agregan demasiada complejidad por muy poca ganancia. En los casos más simples, puede evitar que agregue una manzana a una lista que contiene autos. Multa. Pero sin genéricos, este error arrojaría una ClassCastException realmente rápida en tiempo de ejecución con poco tiempo perdido. Si agrego un automóvil con un asiento para niños con un niño dentro, ¿necesito una advertencia en tiempo de compilación de que la lista es solo para automóviles con asientos para niños que contienen chimpancés bebés? Una lista de instancias de objetos simples comienza a parecer una buena idea.
El código genérico puede tener muchas palabras y caracteres y ocupar mucho espacio extra y tomar mucho más tiempo para leer. Puedo pasar mucho tiempo haciendo que todo ese código extra funcione correctamente. Cuando lo hace, me siento increíblemente inteligente. También he perdido varias horas o más de mi propio tiempo y tengo que preguntarme si alguna vez alguien más podrá descifrar el código. Me gustaría poder entregarlo para mantenimiento a personas que son menos inteligentes que yo o que tienen menos tiempo que perder.
Por otro lado (me sentí un poco mal y siento la necesidad de proporcionar algo de equilibrio), es bueno cuando se usan colecciones y mapas simples para agregar, poner y verificar cuando se escribe, y generalmente no agrega mucho complejidad del código ( si alguien más escribe la colección o el mapa) . Y Java es mejor en esto que C #. Parece que ninguna de las colecciones de C # que quiero usar maneja genéricos. (Admito que tengo gustos extraños en las colecciones).
fuente
Fine. But without generics this error would throw a ClassCastException really quick at run time with little time wasted.
Eso realmente depende de cuánto tiempo de ejecución se produzca entre el inicio del programa y la línea de código en cuestión. Si un usuario informa el error y le toma varios minutos (o, en el peor de los casos, horas o incluso días) reproducir, esa verificación en tiempo de compilación comienza a verse cada vez mejor ...