¿Cuáles son los beneficios del borrado de tipos de Java?


98

Hoy leí un tweet que decía:

Es gracioso cuando los usuarios de Java se quejan del borrado de tipos, que es lo único que Java hizo bien, ignorando todas las cosas que hizo mal.

Entonces mi pregunta es:

¿Hay beneficios del borrado de tipos de Java? ¿Cuáles son los beneficios técnicos o de estilo de programación que (posiblemente) ofrece además de la preferencia de implementaciones de JVM por compatibilidad con versiones anteriores y rendimiento en tiempo de ejecución?


6
Si no desea que su Pregunta se cierre como "basada en opiniones", debe hacerla de otra manera. (Como "cuáles son las ventajas técnicas del borrado de tipos"). Mejor aún, pregúntele al tweeter qué está diciendo realmente.
Stephen C

3
Elimine el "por qué es algo bueno". Lo "bueno" es una caracterización evidentemente subjetiva, y (obviamente) plantea la pregunta de que el borrado de tipos es algo bueno ... lo cual es polémico.
Stephen C

3
Dios nos ayude si este sitio va a degenerar en preguntas sobre todo lo que se ha publicado en Twitter. Al menos cite una fuente confiable para su pregunta.
Marqués de Lorne

21
¿Por qué la pregunta debe provenir de una "fuente confiable"? Eso es una tontería.
vertti

4
Bueno, la mayoría de los problemas aquí no provienen de NINGUNA fuente, excepto los propios problemas de los que preguntan con software / lenguaje / otras cosas específicas, y afortunadamente hay muchos profesionales que "pierden" su tiempo ayudando a estas personas.
vertti

Respuestas:


90

Tipo de borrado es bueno

Vamos a ceñirnos a los hechos

Muchas de las respuestas hasta ahora están demasiado relacionadas con el usuario de Twitter. Es útil concentrarse en los mensajes y no en el mensajero. Hay un mensaje bastante consistente incluso con solo los extractos mencionados hasta ahora:

Es gracioso cuando los usuarios de Java se quejan del borrado de tipos, que es lo único que Java hizo bien, ignorando todas las cosas que hizo mal.

Obtengo enormes beneficios (por ejemplo, parametricidad) y costo nulo (el supuesto costo es un límite de imaginación).

new T es un programa roto. Es isomórfico a la afirmación "todas las proposiciones son verdaderas". No me gusta mucho esto.

Un objetivo: programas razonables

Estos tweets reflejan una perspectiva que no está interesada en si podemos hacer que la máquina haga algo , sino más bien en si podemos razonar que la máquina hará algo que realmente queremos. El buen razonamiento es una prueba. Las pruebas se pueden especificar en notación formal o algo menos formal. Independientemente del lenguaje de especificación, deben ser claros y rigurosos. Las especificaciones informales no son imposibles de estructurar correctamente, pero a menudo tienen fallas en la programación práctica. Terminamos con soluciones como pruebas automatizadas y exploratorias para compensar los problemas que tenemos con el razonamiento informal. Esto no quiere decir que las pruebas sean intrínsecamente una mala idea, pero el usuario de Twitter citado está sugiriendo que hay una manera mucho mejor.

Entonces, nuestro objetivo es tener programas correctos sobre los que podamos razonar de manera clara y rigurosa de una manera que se corresponda con la forma en que la máquina ejecutará el programa. Este, sin embargo, no es el único objetivo. También queremos que nuestra lógica tenga cierto grado de expresividad. Por ejemplo, hay mucho que podemos expresar con lógica proposicional. Es bueno tener una cuantificación universal (∀) y existencial (∃) a partir de algo como la lógica de primer orden.

Usar sistemas de tipos para razonar

Estos objetivos se pueden abordar muy bien mediante sistemas de tipos. Esto es especialmente claro debido a la correspondencia Curry-Howard . Esta correspondencia se expresa a menudo con la siguiente analogía: los tipos son para los programas como los teoremas para las demostraciones.

Esta correspondencia es algo profunda. Podemos tomar expresiones lógicas y traducirlas a través de la correspondencia a tipos. Entonces, si tenemos un programa con el mismo tipo de firma que compila, hemos probado que la expresión lógica es universalmente verdadera (una tautología). Esto se debe a que la correspondencia es bidireccional. La transformación entre el tipo / programa y los mundos de teorema / prueba es mecánica y, en muchos casos, puede automatizarse.

Curry-Howard juega muy bien con lo que nos gustaría hacer con las especificaciones de un programa.

¿Son útiles los sistemas de tipos en Java?

Incluso con una comprensión de Curry-Howard, a algunas personas les resulta fácil descartar el valor de un sistema de tipos, cuando

  1. es extremadamente difícil trabajar con
  2. corresponde (a través de Curry-Howard) a una lógica con expresividad limitada
  3. está roto (lo que lleva a la caracterización de sistemas como "débiles" o "fuertes").

Con respecto al primer punto, quizás los IDE hagan que el sistema de tipos de Java sea lo suficientemente fácil de trabajar (eso es muy subjetivo).

Con respecto al segundo punto, Java pasa a corresponder casi a una lógica de primer orden. Los genéricos dan uso al sistema de tipos equivalente a la cuantificación universal. Desafortunadamente, los comodines solo nos dan una pequeña fracción de cuantificación existencial. Pero la cuantificación universal es un buen comienzo. Es bueno poder decir que las funciones List<A>funcionan universalmente para todas las listas posibles porque A no tiene restricciones. Esto lleva a lo que el usuario de Twitter está hablando con respecto a la "parametricidad".

¡Un artículo que se cita con frecuencia sobre la parametricidad es Philip Wadler's Theorems gratis! . Lo interesante de este artículo es que solo con la firma de tipos, podemos probar algunas invariantes muy interesantes. Si tuviéramos que escribir pruebas automatizadas para estos invariantes, estaríamos perdiendo mucho el tiempo. Por ejemplo, para List<A>, desde la firma de tipo solo paraflatten

<A> List<A> flatten(List<List<A>> nestedLists);

podemos razonar que

flatten(nestedList.map(l -> l.map(any_function)))
     flatten(nestList).map(any_function)

Ese es un ejemplo simple, y probablemente pueda razonar al respecto de manera informal, pero es aún mejor cuando obtenemos tales pruebas de forma formal y gratuita del sistema de tipos y el compilador las verifica.

No borrar puede dar lugar a abusos

Desde la perspectiva de la implementación del lenguaje, los genéricos de Java (que corresponden a tipos universales) influyen mucho en la parametricidad utilizada para obtener pruebas sobre lo que hacen nuestros programas. Esto llega al tercer problema mencionado. Todas estas ganancias de prueba y corrección requieren un sistema de tipo sólido implementado sin defectos. Java definitivamente tiene algunas características de lenguaje que nos permiten romper nuestro razonamiento. Estos incluyen pero no se limitan a:

  • efectos secundarios con un sistema externo
  • reflexión

Los genéricos no borrados están relacionados de muchas formas con la reflexión. Sin borrado, hay información de tiempo de ejecución que se transporta con la implementación que podemos usar para diseñar nuestros algoritmos. Lo que esto significa es que, estadísticamente, cuando razonamos sobre los programas, no tenemos la imagen completa. La reflexión amenaza gravemente la corrección de las pruebas sobre las que razonamos estáticamente. No es una coincidencia que la reflexión también conduzca a una variedad de defectos delicados.

Entonces, ¿de qué maneras los genéricos no borrados podrían ser "útiles"? Consideremos el uso mencionado en el tweet:

<T> T broken { return new T(); }

¿Qué sucede si T no tiene un constructor sin argumentos? En algunos idiomas, lo que obtienes es nulo. O tal vez omita el valor nulo y vaya directamente a generar una excepción (a la que los valores nulos parecen conducir de todos modos). Debido a que nuestro lenguaje es Turing completo, es imposible razonar sobre qué llamadas a brokeninvolucrarán tipos "seguros" con constructores sin argumentos y cuáles no. Hemos perdido la certeza de que nuestro programa funciona universalmente.

Borrar significa que hemos razonado (así que borremos)

Entonces, si queremos razonar sobre nuestros programas, se recomienda encarecidamente que no empleemos características del lenguaje que amenacen fuertemente nuestro razonamiento. Una vez que hacemos eso, ¿por qué no descartar los tipos en tiempo de ejecución? No son necesarios. Podemos obtener algo de eficiencia y simplicidad con la satisfacción de que no fallarán los moldes o de que pueden faltar métodos en la invocación.

Borrar fomenta el razonamiento.


7
En el tiempo que me tomó para improvisar esta respuesta, otras personas respondieron con respuestas similares. Pero habiendo escrito tanto, decidí publicarlo en lugar de descartarlo.
Sukant Hajra

32
Con respeto, el borrado de tipos fue un intento a medias de traer una característica a Java sin romper la compatibilidad con versiones anteriores. No es una fortaleza. Si la principal preocupación fuera que podría intentar crear una instancia de un objeto sin un constructor sin parámetros, la respuesta correcta es permitir una restricción de "tipos con constructores sin parámetros", no invalidar una característica del lenguaje.
Básico

5
Básico, con respeto, no estás haciendo un argumento coherente. Simplemente está afirmando que el borrado es "paralizante" sin tener en cuenta el valor de las pruebas en tiempo de compilación como una herramienta convincente para razonar sobre programas. Además, estos beneficios son completamente ortogonales del camino torcido y la motivación que tomó Java para implementar genéricos. Además, estos beneficios son válidos para los idiomas con una implementación mucho más ilustrada del borrado.
Sukant Hajra

44
El argumento que se presenta aquí no está en contra del borrado, sino en contra de la reflexión (y la verificación de tipo en tiempo de ejecución en general); básicamente afirma que la reflexión es mala, por lo que el hecho de que el borrado no funciona bien con ellos es bueno. Es un punto válido por sí solo (aunque muchas personas no estarían de acuerdo); pero no tiene mucho sentido en contexto, porque Java ya tiene comprobaciones de reflexión / tiempo de ejecución, y no desaparecieron ni desaparecerán. Entonces, tener una construcción que no juegue con esa parte existente y no obsoleta del lenguaje es una limitación, no importa cómo se mire.
Pavel Minaev

10
Tu respuesta es simplemente fuera de tema. Ingresa una explicación larga (aunque interesante en sus propios términos) de por qué el borrado de tipos como concepto abstracto es generalmente útil en el diseño de sistemas de tipos, pero el tema es el beneficio de una característica específica de Java Generics denominada "borrado de tipos ", que solo se parece superficialmente al concepto que usted describe, fallando por completo en proporcionar las invariantes interesantes e introduciendo restricciones irrazonables.
Marko Topolnik

40

Los tipos son una construcción que se utiliza para escribir programas de una manera que permite al compilador verificar la corrección de un programa. Un tipo es una proposición sobre un valor: el compilador verifica que esta proposición sea verdadera.

Durante la ejecución de un programa, no debería haber necesidad de información de tipo; esto ya ha sido verificado por el compilador. El compilador debe tener la libertad de descartar esta información para realizar optimizaciones en el código: hacerlo funcionar más rápido, generar un binario más pequeño, etc. El borrado de los parámetros de tipo facilita esto.

Java rompe la escritura estática al permitir que se consulte la información de tipos en tiempo de ejecución (reflexión, instancia de, etc.). Esto le permite construir programas que no pueden verificarse estáticamente; omiten el sistema de tipos. También pierde oportunidades de optimización estática.

El hecho de que se borren los parámetros de tipo evita que se construyan algunas instancias de estos programas incorrectos; sin embargo, no se permitirían más programas incorrectos si se borrara más información de tipo y se eliminaran el reflejo y la instancia de las instalaciones.

El borrado es importante para mantener la propiedad de "parametricidad" de un tipo de datos. Digamos que tengo un tipo "Lista" parametrizado sobre el tipo de componente T. es decir, Lista <T>. Ese tipo es una proposición de que este tipo de Lista funciona de manera idéntica para cualquier tipo T. El hecho de que T sea un parámetro de tipo abstracto e ilimitado significa que no sabemos nada sobre este tipo, por lo tanto, no podemos hacer nada especial para casos especiales de T.

por ejemplo, digamos que tengo una Lista xs = asList ("3"). Agrego un elemento: xs.add ("q"). Termino con ["3", "q"]. Dado que esto es paramétrico, puedo asumir que List xs = asList (7); xs.add (8) termina con [7,8]. Sé por el tipo que no hace nada para String y nada para Int.

Además, sé que la función List.add no puede inventar valores de T de la nada. Sé que si mi asList ("3") tiene un "7" agregado, las únicas respuestas posibles se construirían a partir de los valores "3" y "7". No hay posibilidad de que se agregue un "2" o una "z" a la lista porque la función no podría construirla. Ninguno de estos otros valores sería sensato de agregar, y la parametricidad evita que se construyan estos programas incorrectos.

Básicamente, el borrado evita algunos medios de violar la parametricidad, eliminando así la posibilidad de programas incorrectos, que es el objetivo de la escritura estática.


15

(Aunque ya escribí una respuesta aquí, revisando esta pregunta dos años después, me doy cuenta de que hay otra forma completamente diferente de responderla, así que dejo la respuesta anterior intacta y agrego esta).


Es muy discutible si el proceso realizado en Java Generics merece el nombre de "borrado de tipo". Dado que los tipos genéricos no se borran sino que se reemplazan con sus contrapartes en bruto, una mejor opción parece ser la "mutilación de tipos".

La característica por excelencia del borrado de tipos en su sentido comúnmente entendido es forzar al tiempo de ejecución a permanecer dentro de los límites del sistema de tipos estáticos haciéndolo "ciego" a la estructura de los datos a los que accede. Esto le da toda la potencia al compilador y le permite probar teoremas basados ​​únicamente en tipos estáticos. También ayuda al programador al restringir los grados de libertad del código, dando más poder al razonamiento simple.

El borrado de tipos de Java no logra eso; paraliza el compilador, como en este ejemplo:

void doStuff(List<Integer> collection) { 
}

void doStuff(List<String> collection) // ERROR: a method cannot have 
                   // overloads which only differ in type parameters

(Las dos declaraciones anteriores colapsan en la misma firma de método después del borrado).

Por otro lado, el tiempo de ejecución aún puede inspeccionar el tipo de un objeto y razonar sobre él, pero dado que su conocimiento del tipo verdadero se ve afectado por el borrado, las violaciones de tipo estático son triviales de lograr y difíciles de prevenir.

Para hacer las cosas aún más complicadas, las firmas de tipo original y borrada coexisten y se consideran en paralelo durante la compilación. Esto se debe a que todo el proceso no se trata de eliminar la información de tipo del tiempo de ejecución, sino de convertir un sistema de tipo genérico en un sistema de tipo sin formato heredado para mantener la compatibilidad con versiones anteriores. Esta joya es un ejemplo clásico:

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)

(Se extends Objecttuvo que agregar el redundante para preservar la compatibilidad con versiones anteriores de la firma borrada).

Ahora, con eso en mente, revisemos la cita:

Es curioso cuando los usuarios de Java se quejan del borrado de tipos, que es lo único que Java hizo bien

¿Qué hizo exactamente Java? ¿Es la palabra en sí, independientemente del significado? Por contraste, eche un vistazo al inttipo humilde : nunca se realiza ninguna verificación de tipo en tiempo de ejecución, ni siquiera es posible, y la ejecución siempre es perfectamente segura para los tipos. Así es como se ve el tipo de borrado cuando se hace correctamente: ni siquiera sabes que está ahí.


10

Lo único que no veo que se considere aquí en absoluto es que el polimorfismo en tiempo de ejecución de OOP depende fundamentalmente de la cosificación de tipos en tiempo de ejecución. Cuando un lenguaje cuya columna vertebral se mantiene en su lugar por tipos refinados introduce una extensión importante a su sistema de tipos y lo basa en el borrado de tipos, la disonancia cognitiva es el resultado inevitable. Esto es precisamente lo que le pasó a la comunidad Java; es por eso que el borrado de tipos ha atraído tanta controversia y, en última instancia, por qué hay planes para deshacerlo en una versión futura de Java . Encontrar algo gracioso en esa queja de los usuarios de Java delata un malentendido honesto del espíritu de Java o una broma de desprecio consciente.

La afirmación "el borrado es lo único que Java hizo bien" implica la afirmación de que "todos los lenguajes basados ​​en el envío dinámico contra el tipo de ejecución del argumento de función son fundamentalmente defectuosos". Aunque ciertamente es un reclamo legítimo en sí mismo, y que incluso puede considerarse como una crítica válida de todos los lenguajes de programación orientada a objetos, incluido Java, no puede presentarse como un punto fundamental desde el cual evaluar y criticar características dentro del contexto de Java , donde el polimorfismo en tiempo de ejecución es axiomático.

En resumen, si bien se puede afirmar válidamente que "el borrado de tipos es el camino a seguir en el diseño del lenguaje", las posiciones que admiten el borrado de tipos dentro de Java están fuera de lugar simplemente porque es mucho, demasiado tarde para eso y ya lo había sido incluso en el momento histórico. cuando Oak fue adoptado por Sun y renombrado a Java.



En cuanto a si la mecanografía estática en sí es la dirección adecuada en el diseño de lenguajes de programación, esto encaja en un contexto filosófico mucho más amplio de lo que creemos que constituye la actividad de programación . Una escuela de pensamiento, que se deriva claramente de la tradición clásica de las matemáticas, ve los programas como instancias de un concepto matemático u otro (proposiciones, funciones, etc.), pero hay una clase de enfoques completamente diferente, que ven la programación como una forma de hablar con la máquina y explicar lo que queremos de ella. Desde este punto de vista, el programa es una entidad dinámica que crece orgánicamente, un dramático opuesto del edificio cuidadosamente construido de un programa de tipo estático.

Parecería natural considerar los lenguajes dinámicos como un paso en esa dirección: la coherencia del programa surge de abajo hacia arriba, sin restricciones a priori que lo impongan de manera descendente. Este paradigma puede verse como un paso hacia el modelado del proceso mediante el cual nosotros, los humanos, nos convertimos en lo que somos a través del desarrollo y el aprendizaje.


Gracias por eso. Es el tipo exacto de comprensión más profunda de las filosofías involucradas que esperaba ver para esta pregunta.
Daniel Langdon

El envío dinámico no depende en absoluto de tipos cosificados. A menudo se basa en lo que la literatura de programación llama "etiquetas", pero incluso entonces, no necesita usar etiquetas. Si crea una interfaz (para un objeto) y crea instancias anónimas de esa interfaz, entonces está realizando un envío dinámico sin necesidad de tipos reificados en absoluto.
Sukant Hajra

@sukanthajra Eso es solo división de cabello. La etiqueta es la información de tiempo de ejecución que necesita para resolver el tipo de comportamiento que exhibe la instancia. Está fuera del alcance del sistema de tipos estáticos, y esa es la esencia del concepto en discusión.
Marko Topolnik

Existe un tipo muy estático diseñado exactamente para este razonamiento llamado "tipo suma". También se le llama "tipo de variante" (en el muy buen libro Tipos y lenguajes de programación de Benjamin Pierce). Así que esto no es para nada la división del cabello. Además, puede utilizar un catamorfismo / plegado / visitante para un enfoque completamente sin etiquetas.
Sukant Hajra

Gracias por la lección de teoría, pero no veo la relevancia. Nuestro tema es el sistema de tipos de Java.
Marko Topolnik

9

Una publicación posterior del mismo usuario en la misma conversación:

new T es un programa roto. Es isomórfico a la afirmación "todas las proposiciones son verdaderas". No me gusta mucho esto.

(Esto fue en respuesta a una declaración de otro usuario, a saber, que "parece que en algunas situaciones 'nueva T' sería mejor", la idea es que new T()es imposible debido al borrado de tipo (esto es discutible, incluso si Testuviera disponible en tiempo de ejecución, podría ser una clase o interfaz abstracta, o podría ser Void, o podría carecer de un constructor sin argumentos, o su constructor sin argumentos podría ser privado (por ejemplo, porque se supone que es una clase singleton), o su El constructor no-arg podría especificar una excepción marcada que el método genérico no detecta o especifica, pero esa era la premisa. Independientemente, es cierto que sin borrar al menos podría escribir T.class.newInstance(), lo que maneja esos problemas.))

Esta visión de que los tipos son isomórficos a las proposiciones sugiere que el usuario tiene experiencia en la teoría formal de tipos. Es muy probable que no le gusten los "tipos dinámicos" o los "tipos en tiempo de ejecución" y preferiría un Java sin abatimientos instanceofy reflexión, etc. (Piense en un lenguaje como Standard ML, que tiene un sistema de tipos muy rico (estático) y cuya semántica dinámica no depende de ningún tipo de información).

Vale la pena teniendo en cuenta, por cierto, que el usuario es curricán: tiempo (s) lo más probable es sinceramente prefiere (estáticamente), lenguajes de tipo (s) que se no sinceramente tratando de persuadir a otros de ese punto de vista. Más bien, el propósito principal del tweet original era burlarse de los que no estaban de acuerdo, y después de que algunos de esos en desacuerdo intervinieran, el usuario publicó tweets de seguimiento como "la razón por la que Java tiene borrado de tipo es que Wadler y otros saben qué que están haciendo, a diferencia de los usuarios de java ". Desafortunadamente, esto hace que sea difícil averiguar qué está pensando en realidad; pero, afortunadamente, también probablemente signifique que no es muy importante hacerlo. Las personas con una profundidad real en sus puntos de vista generalmente no recurren a trolls que son tan libres de contenido.


2
No se compila en Java porque tiene borrado de tipo
Mark Rotteveel

1
@MarkRotteveel: Gracias; Agregué un párrafo para explicar (y comentar) eso.
ruakh

1
A juzgar por la mezcla de votos a favor y en contra, esta es la respuesta más controvertida que he publicado. Estoy extrañamente orgulloso.
ruakh

6

Una cosa buena es que no era necesario cambiar la JVM cuando se introdujeron los genéricos. Java implementa genéricos solo a nivel de compilador.


1
JVM cambia en comparación con qué? Las plantillas tampoco habrían requerido cambios de JVM.
Marqués de Lorne

5

La razón por la que el borrado de tipo es algo bueno es que las cosas que hace imposibles son perjudiciales. Evitar la inspección de argumentos de tipo en tiempo de ejecución facilita la comprensión y el razonamiento sobre los programas.

Una observación que encontré algo contraria a la intuición es que cuando las firmas de funciones son más genéricas, se vuelven más fáciles de entender. Esto se debe a que se reduce el número de posibles implementaciones. Considere un método con esta firma, que de alguna manera sabemos que no tiene efectos secundarios:

public List<Integer> XXX(final List<Integer> l);

¿Cuáles son las posibles implementaciones de esta función? Muchisimo. Puede decir muy poco sobre lo que hace esta función. Podría invertir la lista de entrada. Podría ser emparejar entradas, sumarlas y devolver una lista de la mitad del tamaño. Hay muchas otras posibilidades que se podrían imaginar. Ahora considere:

public <T> List<T> XXX(final List<T> l);

¿Cuántas implementaciones de esta función hay? Dado que la implementación no puede conocer el tipo de los elementos, ahora se puede excluir una gran cantidad de implementaciones: los elementos no se pueden combinar, agregar a la lista o filtrar, et al. Estamos limitados a cosas como: identidad (sin cambios en la lista), eliminar elementos o invertir la lista. Es más fácil razonar sobre esta función basándose solo en su firma.

Excepto… en Java siempre puedes engañar al sistema de tipos. Debido a que la implementación de ese método genérico puede usar cosas como instanceofcomprobaciones y / o conversiones de tipos arbitrarios, nuestro razonamiento basado en la firma de tipo puede volverse inútil fácilmente. La función podría inspeccionar el tipo de elementos y hacer cualquier cantidad de cosas en función del resultado. Si se permiten estos trucos en tiempo de ejecución, las firmas de métodos parametrizados se vuelven mucho menos útiles para nosotros.

Si Java no tuviera borrado de tipo (es decir, los argumentos de tipo se cosificaron en tiempo de ejecución), esto simplemente permitiría más travesuras de este tipo que perjudican el razonamiento. En el ejemplo anterior, la implementación solo puede violar las expectativas establecidas por la firma de tipo si la lista tiene al menos un elemento; pero si Testuviera cosificado, podría hacerlo incluso si la lista estuviera vacía. Los tipos reificados solo aumentarían las (ya muchas) posibilidades de impedir nuestra comprensión del código.

El borrado de tipo hace que el lenguaje sea menos "poderoso". Pero algunas formas de "poder" son realmente dañinas.


1
Parece que está argumentando que los genéricos son demasiado complejos para que los desarrolladores de Java los "entiendan y razonen", o que no se puede confiar en que no se dispararán en el pie si se les da la oportunidad. Este último parece estar sincronizado con el espíritu de Java (sin tipos sin firmar, etc.) pero parece un poco condescendiente para los desarrolladores
Básico

1
@Basic Debo haberme expresado muy mal, porque eso no es lo que quise decir en absoluto. El problema no es que los genéricos sean complejos, es que cosas como la conversión instanceofimpiden nuestra capacidad de razonar sobre lo que hace el código en función de los tipos. Si Java reificara los argumentos de tipo, este problema empeoraría. Borrar tipos en tiempo de ejecución tiene el efecto de hacer que el sistema de tipos sea más útil.
Lachlan

@lachlan, tenía perfecto sentido para mí, y no creo que una lectura normal de él detecte ningún insulto para los desarrolladores de Java.
Rob Grant

3

Esta no es una respuesta directa (OP preguntó "cuáles son los beneficios", yo respondo "cuáles son los contras")

En comparación con el sistema de tipo C #, el borrado de tipo Java es una verdadera molestia por dos razones

No puedes implementar una interfaz dos veces

En C # puede implementar ambos IEnumerable<T1>y de IEnumerable<T2>forma segura, especialmente si los dos tipos no comparten un ancestro común (es decir, su ancestro sí lo es Object ).

Ejemplo práctico: en Spring Framework, no puede implementar ApplicationListener<? extends ApplicationEvent>varias veces. Si necesita diferentes comportamientos en función de lo Tque necesita para probarinstanceof

No puedes hacer una nueva T ()

(y necesitas una referencia a Class para hacer eso)

Como comentaron otros, hacer el equivalente de new T()solo se puede hacer a través de la reflexión, solo invocando una instancia de Class<T>, asegurándose de los parámetros requeridos por el constructor. C # le permite hacerlo new T() solo si se limita Ta un constructor sin parámetros. Si Tno respeta esa restricción, se genera un error de compilación .

En Java, a menudo se verá obligado a escribir métodos que se parecen a los siguientes

public <T> T create(....params, Class<T> classOfT)
{

    ... whatever you do
    ... you will end up
    T = classOfT.newInstance();


    ... or more advanced reflection
    Constructor<T> parameterizedConstructorThatYouKnowAbout = classOfT.getConstructor(...,...);
}

Los inconvenientes del código anterior son:

  • Class.newInstance solo funciona con un constructor sin parámetros. Si no hay ninguno disponible, ReflectiveOperationExceptionse lanza en tiempo de ejecución.
  • El constructor reflejado no resalta problemas en tiempo de compilación como el anterior. Si refactoriza, intercambia argumentos, solo lo sabrá en tiempo de ejecución

Si yo fuera el autor de C #, habría introducido la capacidad de especificar una o más restricciones de constructor que son fáciles de verificar en tiempo de compilación (por lo que puedo requerir, por ejemplo, un constructor con string,stringparámetros). Pero el último es especulación


2

Un punto adicional que ninguna de las otras respuestas parece haber considerado: si realmente necesita genéricos con escritura en tiempo de ejecución , puede implementarlo usted mismo de esta manera:

public class GenericClass<T>
{
     private Class<T> targetClass;
     public GenericClass(Class<T> targetClass)
     {
          this.targetClass = targetClass;
     }

Luego, esta clase puede hacer todas las cosas que se podrían lograr de forma predeterminada si Java no usara el borrado: puede asignar nuevos Ts (suponiendo que Ttenga un constructor que coincida con el patrón que espera usar), o matrices de Ts, puede probar dinámicamente en tiempo de ejecución si un objeto en particular es un Ty cambiar el comportamiento dependiendo de eso, y así sucesivamente.

Por ejemplo:

     public T newT () { 
         try {
             return targetClass.newInstance(); 
         } catch(/* I forget which exceptions can be thrown here */) { ... }
     }

     private T value;
     /** @throws ClassCastException if object is not a T */
     public void setValueFromObject (Object object) {
         value = targetClass.cast(object);
     }
}

¿Alguien quiere explicar el motivo del voto negativo? Esta es, por lo que puedo ver, una explicación perfectamente buena de por qué la supuesta desventaja del sistema de borrado de tipos de Java no es en realidad una limitación real en absoluto. ¿Entonces, cuál es el problema?
Jules

Estoy votando a favor. No es que admita la verificación de tipos en tiempo de ejecución, pero este truco puede ser útil en las minas de sal.
techtangents

3
Java es un lenguaje de las minas de sal, lo que lo convierte en una necesidad básica dada la falta de información sobre el tipo de tiempo de ejecución. Con suerte, el borrado de tipos se deshará en una versión futura de Java, lo que hará que los genéricos sean una característica mucho más útil. Actualmente, el consenso es que su relación empuje / peso está por debajo de 1.
Marko Topolnik

Bueno, claro ... Siempre que su TargetType no use genéricos en sí. Pero eso parece bastante limitante.
Básico

También funciona con tipos genéricos. La única restricción es que si desea crear una instancia, necesita un constructor con los tipos de argumentos correctos, pero todos los demás lenguajes que conozco tienen la misma restricción, por lo que no veo la limitación.
Jules

0

evita el exceso de código similar a C ++ porque el mismo código se usa para varios tipos; sin embargo, el borrado de tipos requiere envío virtual, mientras que el enfoque c ++ - code-bloat puede hacer genéricos no enviados virtualmente


3
Sin embargo, la mayoría de esos despachos virtuales se convierten en estáticos en tiempo de ejecución, por lo que no es tan malo como puede parecer.
Piotr Kołaczkowski

1
Muy cierto, la disponibilidad de un compilador en tiempo de ejecución permite a Java hacer muchas cosas interesantes (y lograr un alto rendimiento) en relación con C ++.
nigromante

wow, quiero escrito en alguna parte java logra un rendimiento alto en relación con CPP ..
jungle_mole

1
@jungle_mole sí, gracias. pocas personas se dan cuenta del beneficio de tener un compilador disponible en tiempo de ejecución para volver a compilar código después de la generación de perfiles. (o que la recolección de basura es grande-oh (no basura) en lugar de la creencia ingenua e incorrecta de que la recolección de basura es grande-oh (basura), (o que si bien la recolección de basura es un esfuerzo leve, compensa al permitir un costo cero asignación)). Es un mundo triste donde la gente adopta ciegamente lo que cree su comunidad y deja de razonar desde los primeros principios.
nigromante

0

La mayoría de las respuestas están más preocupadas por la filosofía de programación que por los detalles técnicos reales.

Y aunque esta pregunta tiene más de 5 años, la pregunta aún persiste: ¿Por qué es deseable el borrado de tipo desde un punto de vista técnico? Al final, la respuesta es bastante simple (en un nivel superior): https://en.wikipedia.org/wiki/Type_erasure

Las plantillas de C ++ no existen en tiempo de ejecución. El compilador emite una versión totalmente optimizada para cada invocación, lo que significa que la ejecución no depende de la información del tipo. Pero, ¿cómo trata un JIT con diferentes versiones de la misma función? ¿No sería mejor tener solo una función? No quisiera que el JIT tuviera que optimizar todas las diferentes versiones del mismo. Bueno, pero ¿qué pasa con la seguridad de tipos? Supongo que tiene que salir por la ventana.

Pero espere un segundo: ¿Cómo lo hace .NET? ¡Reflexión! De esta manera, solo tienen que optimizar una función y también obtener información del tipo de tiempo de ejecución. Y es por eso que los genéricos .NET solían ser más lentos (aunque han mejorado mucho). ¡No estoy argumentando que eso no sea conveniente! Pero es costoso y no debe usarse cuando no es absolutamente necesario (no se considera costoso en lenguajes tipados dinámicamente porque el compilador / intérprete se basa en la reflexión de todos modos).

De esta manera, la programación genérica con borrado de tipo es casi cero sobrecarga (aún se requieren algunas verificaciones / conversiones en tiempo de ejecución): https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.