Genéricos y borrado de tipos


10

Los genéricos en Java se implementan utilizando borrado de tipo. El JLS dice que la inspiración fue la compatibilidad con versiones anteriores. Donde, por otro lado, los genéricos de C # son reificables.

Teóricamente, ¿cuáles son las ventajas y desventajas de tener genéricos como "borrado" o "reificable"?

¿Le falta algo a Java?

Respuestas:


8

Bueno, la motivación (compatibilidad con versiones anteriores) es tanto una ventaja como una desventaja. Es una desventaja porque todos preferiríamos tener tipos reificables, pero el precio a pagar era alto. Considere las opciones de diseño en C #. Tienen tipos reificables, pero ahora tienen API duplicadas. Entonces, imagine una API Java donde también tenemos API duplicadas para cada clase parametrizada. Ahora, imagínese portando miles de líneas de código de clases heredadas a las nuevas clases genéricas. Ahora, ¿quién no consideraría una API duplicada una desventaja? Pero bueno, ¡tienen tipos reificables!

Entonces, la principal motivación fue "evolución, no revolución". Y lógicamente, cada decisión tiene compensaciones.

Además de las otras desventajas mencionadas, también podríamos agregar el hecho de que la eliminación de tipos puede ser difícil de razonar en el momento de la compilación, ya que no es evidente que se eliminen ciertos tipos, y esto conduce a errores muy extraños y difíciles de encontrar.

La existencia de métodos puente (este compilador genera métodos sintácticamente para mantener la compatibilidad binaria) también puede verse como una desventaja. Y estos pueden ser precisamente uno de los motivos de los errores que mencioné en el párrafo anterior.

Las principales desventajas se derivan del hecho ya obvio de que hay una sola clase y no múltiples clases para los tipos genéricos. Como otro ejemplo, considere que sobrecargar un método con la misma clase genérica falla en Java:

public void doSomething(List<One>);
public void doSomething(List<Two>);

Algo que podría verse como una desventaja de los tipos reificables (al menos en C #) es el hecho de que causan la explosión del código . Por ejemplo, List<int>es una clase, y a List<double>es otra totalmente diferente, ya que es a List<string>y a List<MyType>. Por lo tanto, las clases deben definirse en tiempo de ejecución, lo que provoca una explosión de clases y consume recursos valiosos mientras se generan.

Con respecto al hecho de que no es posible definir un new T()en Java, mencionado en otra respuesta, también es interesante considerar que esto no es solo una cuestión de borrado de tipo. También requiere la existencia de un constructor predeterminado, por eso C # requiere una "nueva restricción" para esto. (Consulte Por qué el nuevo T () no es posible en Java , por Alex Buckley).


3
Creo que tengo razón al decir que en el CLR, para el tipo de referencia T s, solo se obtiene una copia del Class<T>código para todos los Ts; más una copia adicional para cada tipo de valor T realmente utilizado.
AakashM

@AakashM: es correcto, hay una sola clase de Tipo de referencia por genérico, sin embargo, cada Tipo de valor crea su propia versión. Esto se debe a que proporciona un espacio de almacenamiento especializado basado en el tipo, todas las referencias toman el mismo almacenamiento, pero los tipos de valor toman un almacenamiento diferente por tipo.
Guvante

6

La desventaja de la eliminación de tipo es que no conoce en tiempo de ejecución el tipo de genérico. Esto implica que no puede aplicar una reflexión sobre ellos y que no puede crear instancias en tiempo de ejecución.

No puedes hacer algo así en Java:

public class MyClass<T> {
    private T t;

    //more methods    
    public void myMethod() {
        //more code
        if (someCondition) {
            t = new T();//illegal
        } else {
            T[] array = new T[];//illegal
        }            
    }       
}

Hay una solución para esto, pero requiere más código. La ventaja, como lo ha mencionado, es la compatibilidad con versiones anteriores.


3

Otra ventaja de los genéricos borrados es que los diferentes lenguajes que se compilan con la JVM emplean diferentes estrategias para los genéricos, por ejemplo, la covarianza del sitio de uso de Scala frente al sitio de uso de Java. Además, los genéricos de clase superior de Scala habrían sido más difíciles de soportar en los tipos reificados de .Net, porque esencialmente Scala en .Net ignoró el formato de reificación incompatible de C #. Si tuviéramos genéricos reificados en la JVM, lo más probable es que esos genéricos reificados no sean adecuados para las características que realmente nos gustan de Scala, y estaríamos atrapados con algo subóptimo. Citando del blog de Ola Bini ,

Lo que todo esto significa es que si desea agregar genéricos reificados a la JVM, debe estar muy seguro de que esa implementación puede abarcar todos los lenguajes estáticos que desean innovar en su propia versión de genéricos y todos los lenguajes dinámicos que desean cree una buena implementación y una buena interfaz con bibliotecas Java. Porque si agrega genéricos reificados que no cumplen con estos criterios, sofocará la innovación y hará que sea mucho más difícil usar la JVM como una VM multilingüe.

Personalmente, no considero la necesidad de usar un TypeTagcontexto vinculado en Scala para métodos sobrecargados conflictivos como una desventaja, porque mueve la sobrecarga y la inflexibilidad de la reificación de un global (programa completo y todos los idiomas posibles) a un sitio de uso por idioma problema que es solo el caso con menos frecuencia.



Otra razón para favorecer los genéricos borrados es porque las dependencias deben inyectarse de una manera que respete la solución al problema de expresión. Si está probando casos específicos de tipos o codificando una fábrica en su función genérica, está haciendo mal la extensibilidad.
Shelby Moore III
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.