¿Cuáles son las diferencias entre los genéricos en C # y Java ... y las plantillas en C ++? [cerrado]


203

Principalmente uso Java y los genéricos son relativamente nuevos. Sigo leyendo que Java tomó la decisión incorrecta o que .NET tiene mejores implementaciones, etc., etc.

Entonces, ¿cuáles son las principales diferencias entre C ++, C #, Java en genéricos? Pros / contras de cada uno?

Respuestas:


364

Agregaré mi voz al ruido y trataré de aclarar las cosas:

Los genéricos de C # le permiten declarar algo como esto.

List<Person> foo = new List<Person>();

y luego el compilador le impedirá poner cosas que no están Personen la lista.
Detrás de escena, el compilador de C # solo se está poniendo List<Person>en el archivo .NET dll, pero en tiempo de ejecución el compilador JIT va y crea un nuevo conjunto de código, como si hubiera escrito una clase de lista especial solo para contener personas, algo así ListOfPerson.

El beneficio de esto es que lo hace realmente rápido. No hay conversión ni ninguna otra cosa, y debido a que el dll contiene la información de la que es una Lista Person, otro código que lo mira más adelante usando la reflexión puede decir que contiene Personobjetos (por lo que obtienes intellisense, etc.).

La desventaja de esto es que el viejo código C # 1.0 y 1.1 (antes de que agregaran genéricos) no comprende estos nuevos List<something>, por lo que debe convertir manualmente las cosas nuevamente a viejas Listpara interactuar con ellos. Este no es un gran problema, porque el código binario C # 2.0 no es compatible con versiones anteriores. La única vez que esto sucederá es si está actualizando algún código antiguo de C # 1.0 / 1.1 a C # 2.0

Java Generics le permite declarar algo como esto.

ArrayList<Person> foo = new ArrayList<Person>();

En la superficie se ve igual, y más o menos lo es. El compilador también le impedirá poner cosas que no están Personen la lista.

La diferencia es lo que sucede detrás de escena. A diferencia de C #, Java no va y crea un especial ListOfPerson, solo usa el viejo y simple ArrayListque siempre ha estado en Java. Cuando saca las cosas de la matriz, Person p = (Person)foo.get(1);todavía tiene que hacerse el baile de casting habitual . El compilador le está ahorrando las pulsaciones de teclas, pero la velocidad de golpe / lanzamiento aún se produce como siempre.
Cuando las personas mencionan "Type Erasure", esto es de lo que están hablando. El compilador inserta los moldes para usted y luego 'borra' el hecho de que está destinado a ser una lista Personno soloObject

El beneficio de este enfoque es que el código antiguo que no comprende los genéricos no tiene que preocuparse. Todavía se trata de lo mismo de ArrayListsiempre. Esto es más importante en el mundo de Java porque querían admitir la compilación de código utilizando Java 5 con genéricos y ejecutarlo en versiones anteriores de 1.4 o JVM anteriores, que Microsoft decidió deliberadamente no molestarse.

La desventaja es el golpe de velocidad que mencioné anteriormente, y también porque no hay ListOfPersonpseudo-clase ni nada de eso en los archivos .class, código que lo mira más adelante (con reflexión, o si lo sacas de otra colección) donde se ha convertido, Objectetc.) no puede decir de ninguna manera que está destinado a ser una lista que contiene solo Persony no cualquier otra lista de matriz.

Las plantillas de C ++ le permiten declarar algo como esto

std::list<Person>* foo = new std::list<Person>();

Parece genéricos C # y Java, y hará lo que crees que debería hacer, pero detrás de escena están sucediendo cosas diferentes.

Tiene mucho más en común con los genéricos de C #, ya que crea elementos especiales en pseudo-classeslugar de simplemente desechar la información de tipo como lo hace Java, pero es una caldera de peces completamente diferente.

Tanto C # como Java producen resultados diseñados para máquinas virtuales. Si escribe algún código que tenga una Personclase, en ambos casos alguna información sobre una Personclase irá al archivo .dll o .class, y la JVM / CLR hará cosas con esto.

C ++ produce código binario x86 sin formato. Todo no es un objeto, y no hay una máquina virtual subyacente que deba saber sobre una Personclase. No hay boxing ni unboxing, y las funciones no tienen que pertenecer a clases, ni a nada.

Debido a esto, el compilador de C ++ no impone restricciones sobre lo que puede hacer con las plantillas: básicamente, cualquier código que pueda escribir manualmente, puede obtener plantillas para que escriba por usted.
El ejemplo más obvio es agregar cosas:

En C # y Java, el sistema genérico necesita saber qué métodos están disponibles para una clase, y necesita pasar esto a la máquina virtual. La única forma de saberlo es codificando la clase real o usando interfaces. Por ejemplo:

string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }

Ese código no se compilará en C # o Java, porque no sabe que el tipo Trealmente proporciona un método llamado Name (). Tienes que decirlo, en C # así:

interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }

Y luego debe asegurarse de que las cosas que pasa a addNames implementen la interfaz IHasName y así sucesivamente. La sintaxis de Java es diferente ( <T extends IHasName>), pero tiene los mismos problemas.

El caso 'clásico' para este problema es intentar escribir una función que haga esto

string addNames<T>( T first, T second ) { return first + second; }

En realidad, no puede escribir este código porque no hay formas de declarar una interfaz con el +método en él. Fallaste.

C ++ no sufre ninguno de estos problemas. Al compilador no le importa pasar tipos a ninguna máquina virtual; si ambos objetos tienen una función .Name (), se compilará. Si no lo hacen, no lo hará. Sencillo.

Entonces, ahí lo tienes :-)


8
Las pseudoclases generadas para los tipos de referencias en C # comparten la misma implementación, por lo que no obtendrá exactamente ListOfPeople. Echa un vistazo a blogs.msdn.com/ericlippert/archive/2009/07/30/…
Piotr Czapla

44
No, puede no compilar Java 5 código utilizando los genéricos, y que se ejecute en edad 1.4 máquinas virtuales (al menos el JDK de Sun no implementa esto. Algunas herramientas de 3 ª parte lo hacen.) Lo que puede hacer es el uso previamente compilado 1.4 JAR desde Código 1.5 / 1.6.
finnw

44
Me opongo a la afirmación de que no puedes escribir int addNames<T>( T first, T second ) { return first + second; }en C #. El tipo genérico se puede restringir a una clase en lugar de una interfaz, y hay una manera de declarar una clase con el +operador en ella.
Mashmagar

44
@AlexanderMalakhov no es idiomático a propósito. El punto no era educar sobre las expresiones idiomáticas de C ++, sino ilustrar cómo cada código maneja de manera diferente el mismo código. Este objetivo habría sido más difícil de lograr cuanto más diferente se vea el código
Orion Edwards

3
@phresnel Estoy de acuerdo en principio, pero si hubiera escrito ese fragmento en C ++ idiomático, sería mucho menos comprensible para los desarrolladores de C # / Java, y por lo tanto (creo) habría hecho un trabajo peor al explicar la diferencia. Acordemos estar en desacuerdo sobre esto :-)
Orion Edwards

61

C ++ rara vez utiliza la terminología "genérica". En cambio, se usa la palabra "plantillas" y es más precisa. Plantillas describe una técnica para lograr un diseño genérico.

Las plantillas de C ++ son muy diferentes de lo que implementan C # y Java por dos razones principales. La primera razón es que las plantillas C ++ no solo permiten argumentos de tipo de tiempo de compilación, sino también argumentos de valor constante de tiempo de compilación: las plantillas se pueden dar como enteros o incluso firmas de funciones. Esto significa que puede hacer algunas cosas bastante funky en tiempo de compilación, por ejemplo, cálculos:

template <unsigned int N>
struct product {
    static unsigned int const VALUE = N * product<N - 1>::VALUE;
};

template <>
struct product<1> {
    static unsigned int const VALUE = 1;
};

// Usage:
unsigned int const p5 = product<5>::VALUE;

Este código también usa la otra característica distinguida de las plantillas de C ++, a saber, la especialización de plantillas. El código define una plantilla de clase, productque tiene un argumento de valor. También define una especialización para esa plantilla que se usa cada vez que el argumento se evalúa como 1. Esto me permite definir una recursión sobre las definiciones de plantilla. Creo que esto fue descubierto por Andrei Alexandrescu .

La especialización de plantillas es importante para C ++ porque permite diferencias estructurales en las estructuras de datos. Las plantillas en su conjunto son un medio para unificar una interfaz entre tipos. Sin embargo, aunque esto es deseable, todos los tipos no pueden ser tratados por igual dentro de la implementación. Las plantillas de C ++ tienen esto en cuenta. Esta es la misma diferencia que OOP hace entre la interfaz y la implementación con la anulación de los métodos virtuales.

Las plantillas de C ++ son esenciales para su paradigma de programación algorítmica. Por ejemplo, casi todos los algoritmos para contenedores se definen como funciones que aceptan el tipo de contenedor como un tipo de plantilla y los tratan de manera uniforme. En realidad, eso no está del todo bien: C ++ no funciona en contenedores, sino en rangos definidos por dos iteradores, que apuntan al principio y detrás del final del contenedor. Por lo tanto, todo el contenido está circunscrito por los iteradores: begin <= elements <end.

El uso de iteradores en lugar de contenedores es útil porque permite operar en partes de un contenedor en lugar de en su conjunto.

Otra característica distintiva de C ++ es la posibilidad de especialización parcial para plantillas de clase. Esto está algo relacionado con la coincidencia de patrones en argumentos en Haskell y otros lenguajes funcionales. Por ejemplo, consideremos una clase que almacena elementos:

template <typename T>
class Store {  }; // (1)

Esto funciona para cualquier tipo de elemento. Pero digamos que podemos almacenar punteros más eficientemente que otros tipos aplicando algún truco especial. Podemos hacer esto especializándonos parcialmente para todos los tipos de puntero:

template <typename T>
class Store<T*> {  }; // (2)

Ahora, cada vez que instanciamos una plantilla de contenedor para un tipo, se usa la definición apropiada:

Store<int> x; // Uses (1)
Store<int*> y; // Uses (2)
Store<string**> z; // Uses (2), with T = string*.

A veces he deseado que la característica genérica en .net permita que otras cosas además de los tipos se usen como claves. Si las matrices de tipo valor formaran parte del Framework (me sorprende que no lo sean, de alguna manera, dada la necesidad de interactuar con API más antiguas que incorporan matrices de tamaño fijo dentro de las estructuras), sería útil declarar un clase que contenía algunos elementos individuales y luego una matriz de tipo de valor cuyo tamaño era un parámetro genérico. Tal como está, lo más cercano que puede llegar es tener un objeto de clase que contenga los elementos individuales y luego también contenga una referencia a un objeto separado que contenga la matriz.
supercat

@supercat Si interactúa con la API heredada, la idea es utilizar el cálculo de referencias (que se puede anotar mediante atributos). El CLR no tiene matrices de tamaño fijo de todos modos, por lo que tener argumentos de plantilla que no sean de tipo no sería de ayuda aquí.
Konrad Rudolph el

Supongo que lo que me resulta desconcertante es que parece que tener matrices de tipo de valor de tamaño fijo no debería haber sido difícil, y habría permitido que muchos tipos de datos se clasifiquen por referencia en lugar de por valor. Si bien Marrshal-by-value puede ser útil en casos que realmente no pueden manejarse de otra manera, consideraría que Marrshal-by-ref es superior en casi todos los casos donde es utilizable, por lo que permitir que tales casos incluyan estructuras con de gran tamaño habrían parecido una característica útil.
supercat

Por cierto, otra situación en la que los parámetros genéricos que no son de tipo sería útil con los tipos de datos que representan cantidades dimensionadas. Se podría incluir información dimensional dentro de las instancias que representan cantidades, pero tener dicha información dentro de un tipo permitiría especificar que una colección debe contener objetos que representan una unidad dimensionada particular.
supercat


18

Ya hay muchas buenas respuestas sobre cuáles son las diferencias, así que permítanme dar una perspectiva ligeramente diferente y agregar el por qué .

Como ya se explicó, la principal diferencia es la eliminación de tipos , es decir, el hecho de que el compilador de Java borra los tipos genéricos y no terminan en el código de bytes generado. Sin embargo, la pregunta es: ¿por qué alguien haría eso? ¡No tiene sentido! O lo hace?

Bueno, cual es la alternativa? Si no implementa genéricos en el idioma, ¿ dónde los implementa? Y la respuesta es: en la máquina virtual. Lo que rompe la compatibilidad con versiones anteriores.

El borrado de tipo, por otro lado, le permite mezclar clientes genéricos con bibliotecas no genéricas. En otras palabras: el código que se compiló en Java 5 todavía se puede implementar en Java 1.4.

Microsoft, sin embargo, decidió romper la compatibilidad con versiones anteriores para los genéricos. Es por eso que .NET Generics es "mejor" que Java Generics.

Por supuesto, Sun no son idiotas ni cobardes. La razón por la que se "acobardaron" fue que Java era significativamente más antiguo y estaba más extendido que .NET cuando introdujeron los genéricos. (Se introdujeron aproximadamente al mismo tiempo en ambos mundos). Romper la compatibilidad con versiones anteriores habría sido un gran dolor.

Dicho de otra manera: en Java, los genéricos son parte del lenguaje (lo que significa que se aplican solo a Java, no a otros idiomas), en .NET son parte de la máquina virtual (lo que significa que se aplican a todos los idiomas, no solo C # y Visual Basic.NET).

Compare esto con las características de .NET como LINQ, expresiones lambda, inferencia de tipos de variables locales, tipos anónimos y árboles de expresiones: estas son todas las características del lenguaje . Es por eso que hay diferencias sutiles entre VB.NET y C #: si esas características fueran parte de la VM, serían las mismas en todos los idiomas. Pero el CLR no ha cambiado: sigue siendo el mismo en .NET 3.5 SP1 que en .NET 2.0. Puede compilar un programa C # que use LINQ con el compilador .NET 3.5 y aún ejecutarlo en .NET 2.0, siempre que no use ninguna biblioteca .NET 3.5. Eso no trabajo con los genéricos y .NET 1.1, pero sería trabajar con Java y Java 1.4.


3
LINQ es principalmente una función de biblioteca (aunque C # y VB también agregaron azúcar de sintaxis junto a ella). Cualquier lenguaje dirigido al CLR 2.0 puede obtener un uso completo de LINQ simplemente cargando el ensamblado System.Core.
Richard Berg el

Sí, lo siento, debería haber sido más claro wrt. LINQ. Me refería a la sintaxis de consulta, no a los operadores de consulta estándar monádicos, los métodos de extensión LINQ o la interfaz IQueryable. Obviamente, puede usarlos desde cualquier lenguaje .NET.
Jörg W Mittag el

1
Estoy pensando en otra opción para Java. Incluso Oracle no quiere romper la compatibilidad con versiones anteriores, aún puede hacer algún truco de compilación para evitar que se borre la información de tipo. Por ejemplo, ArrayList<T>se puede emitir como un nuevo tipo con nombre interno con un Class<T>campo estático (oculto) . Siempre que la nueva versión de lib genérica se haya implementado con el código de byte 1.5+, podrá ejecutarse en 1.4 JVM.
Earth Engine

14

Seguimiento de mi publicación anterior.

Las plantillas son una de las principales razones por las que C ++ falla tan abismalmente en intellisense, independientemente del IDE utilizado. Debido a la especialización de la plantilla, el IDE nunca puede estar realmente seguro de si un miembro dado existe o no. Considerar:

template <typename T>
struct X {
    void foo() { }
};

template <>
struct X<int> { };

typedef int my_int_type;

X<my_int_type> a;
a.|

Ahora, el cursor está en la posición indicada y es muy difícil para el IDE decir en ese momento si, y qué, tienen los miembros a. Para otros lenguajes, el análisis sería sencillo, pero para C ++, se necesita bastante evaluación de antemano.

Se pone peor. ¿Qué pasaría si también my_int_typese definiera dentro de una plantilla de clase? Ahora su tipo dependería de otro argumento de tipo. Y aquí, incluso los compiladores fallan.

template <typename T>
struct Y {
    typedef T my_type;
};

X<Y<int>::my_type> b;

Después de pensar un poco, un programador concluiría que este código es el mismo que el anterior: Y<int>::my_typeresuelve int, por blo tanto, debe ser del mismo tipo que a, ¿verdad?

Incorrecto. En el punto donde el compilador intenta resolver esta declaración, ¡aún no lo sabe Y<int>::my_type! Por lo tanto, no sabe que este es un tipo. Podría ser otra cosa, por ejemplo, una función miembro o un campo. Esto puede generar ambigüedades (aunque no en el presente caso), por lo tanto, el compilador falla. Tenemos que decirle explícitamente que nos referimos a un nombre de tipo:

X<typename Y<int>::my_type> b;

Ahora, el código se compila. Para ver cómo surgen las ambigüedades de esta situación, considere el siguiente código:

Y<int>::my_type(123);

Esta declaración de código es perfectamente válida y le dice a C ++ que ejecute la llamada a la función Y<int>::my_type. Sin embargo, si my_typeno es una función, sino un tipo, esta declaración aún sería válida y realizaría una conversión especial (la conversión de estilo de función) que a menudo es una invocación de constructor. El compilador no puede decir a qué nos referimos, así que tenemos que desambiguar aquí.


2
Estoy muy de acuerdo. Sin embargo, hay algo de esperanza. El sistema de autocompletado y el compilador de C ++ deben interactuar muy de cerca. Estoy bastante seguro de que Visual Studio nunca tendrá esa característica, pero podrían ocurrir cosas en Eclipse / CDT o algún otro IDE basado en GCC. ESPERANZA ! :)
Benoît

6

Tanto Java como C # introdujeron genéricos después de su lanzamiento en el primer idioma. Sin embargo, existen diferencias en cómo cambiaron las bibliotecas principales cuando se introdujeron los genéricos. Los genéricos de C # no son solo magia de compilación, por lo que no fue posible generar clases de biblioteca existentes sin romper la compatibilidad con versiones anteriores.

Por ejemplo, en Java, el Framework de Colecciones existente estaba completamente genérico . Java no tiene una versión genérica y una versión no genérica heredada de las clases de colecciones. De alguna manera, esto es mucho más limpio: si necesita usar una colección en C #, realmente hay muy pocas razones para usar la versión no genérica, pero esas clases heredadas permanecen en su lugar, abarrotando el paisaje.

Otra diferencia notable son las clases Enum en Java y C #. Enum de Java tiene esta definición de aspecto algo tortuosa:

//  java.lang.Enum Definition in Java
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {

(vea la explicación muy clara de Angelika Langer de exactamente por qué esto es así. Esencialmente, esto significa que Java puede dar acceso seguro de tipo desde una cadena a su valor Enum:

//  Parsing String to Enum in Java
Colour colour = Colour.valueOf("RED");

Compare esto con la versión de C #:

//  Parsing String to Enum in C#
Colour colour = (Colour)Enum.Parse(typeof(Colour), "RED");

Como Enum ya existía en C # antes de que se introdujeran los genéricos en el lenguaje, la definición no podía cambiar sin romper el código existente. Entonces, al igual que las colecciones, permanece en las bibliotecas principales en este estado heredado.


Incluso los genéricos de C # no son solo magia de compilación, el compilador puede hacer más magia para generar la biblioteca existente. No hay ninguna razón por qué tienen que cambiar el nombre ArrayListde List<T>y ponerlo en un nuevo espacio de nombres. El hecho es que si apareciera una clase en el código fuente, ya ArrayList<T>que se convertirá en un nombre de clase generado por el compilador diferente en el código IL, entonces no puede haber conflictos de nombres.
Earth Engine

4

11 meses de retraso, pero creo que esta pregunta está lista para algunas cosas de Java Wildcard.

Esta es una característica sintáctica de Java. Supongamos que tiene un método:

public <T> void Foo(Collection<T> thing)

Y suponga que no necesita hacer referencia al tipo T en el cuerpo del método. Estás declarando un nombre T y luego solo lo usas una vez, entonces ¿por qué deberías pensar en un nombre para él? En cambio, puedes escribir:

public void Foo(Collection<?> thing)

El signo de interrogación le pide al compilador que finja que usted declaró un parámetro de tipo con nombre normal que solo necesita aparecer una vez en ese lugar.

No hay nada que pueda hacer con comodines que tampoco pueda hacer con un parámetro de tipo con nombre (que es cómo se hacen siempre estas cosas en C ++ y C #).


2
Otros 11 meses de retraso ... Hay cosas que puede hacer con comodines Java que no puede hacer con parámetros de tipo con nombre. Puede hacer esto en Java: class Foo<T extends List<?>>y usar, Foo<StringList>pero en C # debe agregar ese parámetro de tipo adicional: class Foo<T, T2> where T : IList<T2>y usar el torpe Foo<StringList, String>.
R. Martinho Fernandes



1

Las plantillas C ++ son en realidad mucho más potentes que sus contrapartes C # y Java, ya que se evalúan en tiempo de compilación y se especializan en soporte. Esto permite la metaprogramación de plantillas y hace que el compilador de C ++ sea equivalente a una máquina de Turing (es decir, durante el proceso de compilación puede calcular cualquier cosa que sea computable con una máquina de Turing).


1

En Java, los genéricos son solo de nivel de compilador, por lo que obtienes:

a = new ArrayList<String>()
a.getClass() => ArrayList

Tenga en cuenta que el tipo de 'a' es una lista de matriz, no una lista de cadenas. Entonces, el tipo de una lista de plátanos sería igual a () una lista de monos.

Por así decirlo.


1

Parece que, entre otras propuestas muy interesantes, hay una sobre refinar genéricos y romper la compatibilidad con versiones anteriores:

Actualmente, los genéricos se implementan mediante el borrado, lo que significa que la información de tipo genérico no está disponible en tiempo de ejecución, lo que dificulta la escritura de algún tipo de código. Los genéricos se implementaron de esta manera para admitir la compatibilidad con códigos anteriores no genéricos. Los genéricos reificados pondrían a disposición la información del tipo genérico en tiempo de ejecución, lo que rompería el código heredado no genérico. Sin embargo, Neal Gafter ha propuesto hacer que los tipos sean reifiable solo si se especifica, para no romper la compatibilidad con versiones anteriores.

en el artículo de Alex Miller sobre las propuestas de Java 7


0

NB: no tengo suficiente punto para comentar, así que siéntase libre de mover esto como un comentario para responder adecuadamente.

Contrariamente a la creencia popular, que nunca entiendo de dónde vino, .net implementó genéricos verdaderos sin romper la compatibilidad con versiones anteriores, y dedicaron un esfuerzo explícito para eso. No tiene que cambiar su código .net 1.0 no genérico a genéricos solo para usarlo en .net 2.0. Las listas genéricas y no genéricas todavía están disponibles en .Net framework 2.0 incluso hasta 4.0, exactamente por nada más que por razones de compatibilidad con versiones anteriores. Por lo tanto, los códigos antiguos que todavía usaban ArrayList no genérico seguirán funcionando y usan la misma clase ArrayList que antes. La compatibilidad con el código hacia atrás siempre se mantiene desde 1.0 hasta ahora ... Por lo tanto, incluso en .net 4.0, todavía tiene la opción de usar cualquier clase no genérica de 1.0 BCL si elige hacerlo.

Por lo tanto, no creo que Java tenga que romper la compatibilidad con versiones anteriores para admitir genéricos verdaderos.


Ese no es el tipo de compatibilidad con versiones anteriores de la que habla la gente. La idea es la compatibilidad con versiones anteriores para el tiempo de ejecución : el código escrito con genéricos en .NET 2.0 no se puede ejecutar en versiones anteriores de .NET framework / CLR. Del mismo modo, si Java introdujera genéricos "verdaderos", el código Java más nuevo no podría ejecutarse en JVM más antiguas (porque requiere cambios importantes en el código de bytes).
tzaman

Eso es .net, no genéricos. Siempre requiere recompilación para apuntar a una versión CLR específica. Hay compatibilidad de código de bytes, hay compatibilidad de código. Y también, respondía específicamente con respecto a la necesidad de convertir el código antiguo que estaba usando la Lista anterior para usar la nueva Lista de genéricos, lo cual no es cierto en absoluto.
Sheepy el

1
Creo que la gente está hablando de compatibilidad hacia adelante . Es decir, el código .net 2.0 para ejecutarse en .net 1.1, que se romperá porque el tiempo de ejecución 1.1 no sabe nada acerca de la "pseudo-clase" 2.0. ¿No debería ser entonces que "Java no implementa genéricos verdaderos porque quieren mantener la compatibilidad con versiones anteriores"? (en lugar de hacia atrás)
Sheepy

Los problemas de compatibilidad son sutiles. No creo que el problema sea que agregar genéricos "reales" a Java afectaría a cualquier programa que use versiones anteriores de Java, sino que el código que usó genéricos "nuevos y mejorados" tendría dificultades para intercambiar dichos objetos con código anterior que No sabía nada sobre los nuevos tipos. Supongamos, por ejemplo, que un programa tiene un ArrayList<Foo>que quiere pasar a un método anterior que supuestamente debe llenar ArrayListcon instancias de Foo. Si un ArrayList<foo>no es un ArrayList, ¿cómo se hace que eso funcione?
supercat
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.