Respuestas:
Hay una gran diferencia entre ellos. En C ++ no tiene que especificar una clase o una interfaz para el tipo genérico. Es por eso que puede crear funciones y clases verdaderamente genéricas, con la advertencia de una escritura más flexible.
template <typename T> T sum(T a, T b) { return a + b; }
El método anterior agrega dos objetos del mismo tipo y se puede usar para cualquier tipo T que tenga disponible el operador "+".
En Java, debe especificar un tipo si desea llamar a métodos en los objetos pasados, algo como:
<T extends Something> T sum(T a, T b) { return a.add ( b ); }
En C ++, las funciones / clases genéricas solo se pueden definir en encabezados, ya que el compilador genera diferentes funciones para diferentes tipos (con los que se invoca). Entonces la compilación es más lenta. En Java, la compilación no tiene una penalización importante, pero Java utiliza una técnica llamada "borrado" donde el tipo genérico se borra en tiempo de ejecución, por lo que en tiempo de ejecución Java en realidad está llamando ...
Something sum(Something a, Something b) { return a.add ( b ); }
Entonces, la programación genérica en Java no es realmente útil, solo es un poco de azúcar sintáctica para ayudar con la nueva construcción foreach.
EDITAR: la opinión anterior sobre la utilidad fue escrita por un yo más joven. Los genéricos de Java ayudan con la seguridad de tipos, por supuesto.
extends
o super
. La respuesta es incorrecta,
Los genéricos de Java son enormemente diferentes a las plantillas de C ++.
Básicamente, en C ++ las plantillas son básicamente un conjunto preprocesador / macro glorificado ( Nota: dado que algunas personas parecen incapaces de comprender una analogía, no estoy diciendo que el procesamiento de plantillas sea una macro). En Java, son básicamente azúcar sintáctica para minimizar la conversión de objetos en repeticiones. Aquí hay una introducción bastante decente a las plantillas de C ++ frente a los genéricos de Java .
Para elaborar sobre este punto: cuando usa una plantilla de C ++, básicamente está creando otra copia del código, como si usara una #define
macro. Esto le permite hacer cosas como tener int
parámetros en las definiciones de plantilla que determinan los tamaños de las matrices y demás.
Java no funciona así. En Java, todos los objetos se extienden desde java.lang.Object , entonces, pre-Generics, escribirías un código como este:
public class PhoneNumbers {
private Map phoneNumbers = new HashMap();
public String getPhoneNumber(String name) {
return (String)phoneNumbers.get(name);
}
...
}
porque todos los tipos de colección Java usaban Object como su tipo base para que pudieras poner cualquier cosa en ellos. Java 5 se implementa y agrega genéricos para que pueda hacer cosas como:
public class PhoneNumbers {
private Map<String, String> phoneNumbers = new HashMap<String, String>();
public String getPhoneNumber(String name) {
return phoneNumbers.get(name);
}
...
}
Y eso es todo lo que son los Java Generics: envoltorios para lanzar objetos. Eso es porque los Java Generics no están refinados. Usan borrado tipo. Esta decisión se tomó porque Java Generics llegó tan tarde en la pieza que no querían romper la compatibilidad con versiones anteriores (a Map<String, String>
se puede usar siempre que Map
se solicite). Compare esto con .Net / C #, donde no se usa el borrado de tipos, lo que conduce a todo tipo de diferencias (por ejemplo, puede usar tipos primitivos IEnumerable
y IEnumerable<T>
no tener relación entre sí).
Y una clase que usa genéricos compilados con un compilador Java 5+ se puede usar en JDK 1.4 (suponiendo que no use ninguna otra característica o clase que requiera Java 5+).
Es por eso que los genéricos de Java se llaman azúcar sintáctico .
Pero esta decisión sobre cómo hacer genéricos tiene efectos tan profundos que las (excelentes) preguntas frecuentes sobre genéricos de Java han surgido para responder las muchas, muchas preguntas que las personas tienen sobre los genéricos de Java.
Las plantillas de C ++ tienen una serie de características que los genéricos de Java no tienen:
Uso de argumentos de tipo primitivo.
Por ejemplo:
template<class T, int i>
class Matrix {
int T[i][i];
...
}
Java no permite el uso de argumentos de tipo primitivo en genéricos.
Uso de argumentos de tipo predeterminados , que es una característica que extraño en Java pero hay razones de compatibilidad con versiones anteriores para esto;
Por ejemplo:
public class ObservableList<T extends List> {
...
}
Realmente es necesario enfatizar que las invocaciones de plantillas con diferentes argumentos realmente son de diferentes tipos. Ni siquiera comparten miembros estáticos. En Java este no es el caso.
Además de las diferencias con los genéricos, para completar, aquí hay una comparación básica de C ++ y Java (y otra ).
Y también puedo sugerir pensar en Java . Como programador de C ++, muchos de los conceptos, como los objetos, ya serán una segunda naturaleza, pero existen diferencias sutiles, por lo que puede valer la pena tener un texto introductorio, incluso si descrema partes.
Mucho de lo que aprenderá cuando aprenda Java son todas las bibliotecas (tanto estándar, lo que viene en el JDK, como no estándar, que incluye cosas de uso común como Spring). La sintaxis de Java es más detallada que la sintaxis de C ++ y no tiene muchas características de C ++ (por ejemplo, sobrecarga de operadores, herencia múltiple, mecanismo destructor, etc.), pero eso tampoco lo convierte estrictamente en un subconjunto de C ++.
Map map = new HashMap<String, String>
. Significa que puede implementar un nuevo código en una JVM anterior y se ejecutará debido a las similitudes en el código de bytes.
C ++ tiene plantillas. Java tiene genéricos, que se parecen a las plantillas de C ++, pero son muy, muy diferentes.
Las plantillas funcionan, como su nombre lo indica, al proporcionar al compilador una plantilla (espere ...) que puede usar para generar código de tipo seguro al completar los parámetros de la plantilla.
Los genéricos, tal como los entiendo, funcionan al revés: el compilador usa los parámetros de tipo para verificar que el código que los usa es seguro, pero el código resultante se genera sin tipos.
Piense en las plantillas de C ++ como un sistema macro realmente bueno , y los genéricos de Java como una herramienta para generar automáticamente los tipos de letra.
const
. Un objeto en C ++ no se modificará a través de un const
puntero a menos que se const
elimine el -ness. Del mismo modo, las conversiones implícitas creadas por tipos genéricos en Java están garantizadas como "seguras" a menos que los parámetros de tipo se eliminen manualmente en algún lugar del código.
Otra característica que tienen las plantillas C ++ que los genéricos de Java no tienen es la especialización. Eso le permite tener una implementación diferente para tipos específicos. Por lo tanto, puede tener, por ejemplo, una versión altamente optimizada para un int , a la vez que tiene una versión genérica para el resto de los tipos. O puede tener diferentes versiones para los tipos puntero y no puntero. Esto es útil si desea operar en el objeto desreferenciado cuando se le entrega un puntero.
Hay una gran explicación de este tema en Java Generics and Collections Por Maurice Naftalin, Philip Wadler. Recomiendo altamente este libro. Citar:
Los genéricos en Java se parecen a las plantillas en C ++. ... La sintaxis es deliberadamente similar y la semántica es deliberadamente diferente. ... Semánticamente, los genéricos de Java se definen por borrado, mientras que las plantillas de C ++ se definen por expansión.
Por favor, lea la explicación completa aquí .
(fuente: oreilly.com )
Básicamente, las plantillas AFAIK, C ++ crean una copia del código para cada tipo, mientras que los genéricos de Java usan exactamente el mismo código.
Sí, puede decir que la plantilla de C ++ es equivalente al concepto genérico de Java (aunque sería más apropiado decir que los genéricos de Java son equivalentes a C ++ en concepto)
Si está familiarizado con el mecanismo de plantilla de C ++, podría pensar que los genéricos son similares, pero la similitud es superficial. Los genéricos no generan una nueva clase para cada especialización, ni permiten la "metaprogramación de plantillas".
de: Java Generics
Los genéricos de Java (y C #) parecen ser un simple mecanismo de sustitución de tipo en tiempo de ejecución.
Las plantillas de C ++ son una construcción en tiempo de compilación que le brinda una forma de modificar el lenguaje para satisfacer sus necesidades. En realidad, son un lenguaje puramente funcional que el compilador ejecuta durante una compilación.
Otra ventaja de las plantillas de C ++ es la especialización.
template <typename T> T sum(T a, T b) { return a + b; }
template <typename T> T sum(T* a, T* b) { return (*a) + (*b); }
Special sum(const Special& a, const Special& b) { return a.plus(b); }
Ahora, si llama a suma con punteros, se llamará al segundo método, si llama a suma con objetos sin puntero, se llamará al primer método y si llama sum
a Special
objetos, se llamará al tercero. No creo que esto sea posible con Java.
Lo resumiré en una sola oración: las plantillas crean nuevos tipos, los genéricos restringen los tipos existentes.
@Keith:
Ese código es realmente incorrecto y, aparte de las fallas más pequeñas ( template
omitidas, la sintaxis de especialización tiene un aspecto diferente), la especialización parcial no funciona en plantillas de función, solo en plantillas de clase. Sin embargo, el código funcionaría sin una especialización parcial de la plantilla, en lugar de usar una sobrecarga simple y antigua:
template <typename T> T sum(T a, T b) { return a + b; }
template <typename T> T sum(T* a, T* b) { return (*a) + (*b); }
La respuesta a continuación es del libro Cracking The Coding Interview Solutions para el Capítulo 13, que creo que es muy bueno.
La implementación de los genéricos de Java se basa en una idea de "borrado de tipo:" Esta técnica elimina los tipos parametrizados cuando el código fuente se traduce al código de bytes de la máquina virtual Java (JVM). Por ejemplo, suponga que tiene el código de Java a continuación:
Vector<String> vector = new Vector<String>();
vector.add(new String("hello"));
String str = vector.get(0);
Durante la compilación, este código se reescribe en:
Vector vector = new Vector();
vector.add(new String("hello"));
String str = (String) vector.get(0);
El uso de genéricos de Java realmente no cambió mucho acerca de nuestras capacidades; solo hizo las cosas un poco más bonitas. Por esta razón, los genéricos de Java a veces se denominan "azúcar sintáctico".
Esto es bastante diferente de C ++. En C ++, las plantillas son esencialmente un conjunto de macros glorificado, con el compilador creando una nueva copia del código de plantilla para cada tipo. Prueba de ello es el hecho de que una instancia de MyClass no compartirá una variable estática con MyClass. Sin embargo, las instancias de remolque de MyClass compartirán una variable estática.
/*** MyClass.h ***/
template<class T> class MyClass {
public:
static int val;
MyClass(int v) { val v;}
};
/*** MyClass.cpp ***/
template<typename T>
int MyClass<T>::bar;
template class MyClass<Foo>;
template class MyClass<Bar>;
/*** main.cpp ***/
MyClass<Foo> * fool
MyClass<Foo> * foo2
MyClass<Bar> * barl
MyClass<Bar> * bar2
new MyClass<Foo>(10);
new MyClass<Foo>(15);
new MyClass<Bar>(20);
new MyClass<Bar>(35);
int fl fool->val; // will equal 15
int f2 foo2->val; // will equal 15
int bl barl->val; // will equal 35
int b2 bar2->val; // will equal 35
En Java, las variables estáticas se comparten entre instancias de MyClass, independientemente de los diferentes parámetros de tipo.
Los genéricos de Java y las plantillas de C ++ tienen otras diferencias. Éstos incluyen:
Las plantillas no son más que un sistema macro. Sintaxis de azúcar. Se expanden completamente antes de la compilación real (o, al menos, los compiladores se comportan como si fuera el caso).
Ejemplo:
Digamos que queremos dos funciones. Una función toma dos secuencias (lista, matrices, vectores, lo que sea que vaya) de números, y devuelve su producto interno. Otra función toma una longitud, genera dos secuencias de esa longitud, las pasa a la primera función y devuelve su resultado. El problema es que podríamos cometer un error en la segunda función, por lo que estas dos funciones no son realmente de la misma longitud. Necesitamos el compilador para advertirnos en este caso. No cuando el programa se está ejecutando, sino cuando se está compilando.
En Java puedes hacer algo como esto:
import java.io.*;
interface ScalarProduct<A> {
public Integer scalarProduct(A second);
}
class Nil implements ScalarProduct<Nil>{
Nil(){}
public Integer scalarProduct(Nil second) {
return 0;
}
}
class Cons<A implements ScalarProduct<A>> implements ScalarProduct<Cons<A>>{
public Integer value;
public A tail;
Cons(Integer _value, A _tail) {
value = _value;
tail = _tail;
}
public Integer scalarProduct(Cons<A> second){
return value * second.value + tail.scalarProduct(second.tail);
}
}
class _Test{
public static Integer main(Integer n){
return _main(n, 0, new Nil(), new Nil());
}
public static <A implements ScalarProduct<A>>
Integer _main(Integer n, Integer i, A first, A second){
if (n == 0) {
return first.scalarProduct(second);
} else {
return _main(n-1, i+1,
new Cons<A>(2*i+1,first), new Cons<A>(i*i, second));
//the following line won't compile, it produces an error:
//return _main(n-1, i+1, first, new Cons<A>(i*i, second));
}
}
}
public class Test{
public static void main(String [] args){
System.out.print("Enter a number: ");
try {
BufferedReader is =
new BufferedReader(new InputStreamReader(System.in));
String line = is.readLine();
Integer val = Integer.parseInt(line);
System.out.println(_Test.main(val));
} catch (NumberFormatException ex) {
System.err.println("Not a valid number");
} catch (IOException e) {
System.err.println("Unexpected IO ERROR");
}
}
}
En C # puedes escribir casi lo mismo. Intente reescribirlo en C ++, y no se compilará, quejándose de una expansión infinita de plantillas.
Me gustaría citar askanydifference aquí:
La principal diferencia entre C ++ y Java radica en su dependencia de la plataforma. Mientras que C ++ es un lenguaje dependiente de la plataforma, Java es un lenguaje independiente de la plataforma.
La declaración anterior es la razón por la cual C ++ puede proporcionar verdaderos tipos genéricos. Si bien Java tiene una comprobación estricta y, por lo tanto, no permite el uso de genéricos de la manera en que C ++ lo permite.