¿Cuáles son las diferencias entre los tipos "genéricos" en C ++ y Java?

Respuestas:

144

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.

Alexandru Nedelcu
fuente
27
Tiene toda la razón en que es solo un elaborado azúcar sintáctico.
alphazero
31
No es azúcar puramente sintáctico. El compilador usa esta información para verificar los tipos. Aunque la información no está disponible en tiempo de ejecución, no llamaría a algo que el compilado usa simplemente "azúcar sintáctico". Si desea llamar así, bueno, entonces C es simplemente azúcar sintáctica para el montaje, y eso es sólo el azúcar sintáctica para código de máquina :)
DTECH
42
Creo que el azúcar sintáctico es útil.
poitroae
55
Se perdió un punto importante de diferencia, lo que puede usar para crear una instancia genérica. En c ++ es posible usar la plantilla <int N> y obtener un resultado diferente para cualquier número usado para instanciarlo. Se utiliza para metaprogramación en tiempo de compilación. Como la respuesta en: stackoverflow.com/questions/189172/c-templates-turing-complete
stonemetal
2
Usted no tiene que 'especificar un tipo', en la forma de extendso super. La respuesta es incorrecta,
Marqués de Lorne
124

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 #definemacro. Esto le permite hacer cosas como tener intpará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 Mapse 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 IEnumerabley 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;

  • Java permite el límite de argumentos.

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 ++.

cletus
fuente
1
No son equivalentes en concepto. El mejor ejemplo es el patrón de plantilla curiosamente recurrente. El segundo mejor es el diseño orientado a políticas. El tercer mejor es el hecho de que C ++ permite pasar números enteros entre paréntesis angulares (myArray <5>).
Max Lybbert
1
No, no son equivalentes en concepto. Hay cierta superposición en el concepto, pero no mucho. Ambos le permiten crear Lista <T>, pero eso es lo más lejos que puede llegar. Las plantillas de C ++ van mucho más allá.
jalf
55
Es importante tener en cuenta que el problema de borrado de tipo significa más que solo la compatibilidad con versiones anteriores 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.
Yuval Adam
1
Notarás que dije "básicamente un preprocesador / macro glorificado". Fue una analogía porque cada declaración de plantilla creará más código (a diferencia de Java / C #).
cletus
44
El código de plantilla es muy diferente a copiar y pegar. Si piensa en términos de expansión macro, tarde o temprano se verá afectado por errores sutiles como este: womble.decadentplace.org.uk/c++/…
Nemanja Trifunovic
86

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.

 

Shog9
fuente
44
Esta es una explicación bastante buena y concisa. Un ajuste que me sentiría tentado a hacer es que los genéricos de Java son una herramienta para generar automáticamente tipos de letra que están garantizados para ser seguros (con algunas condiciones). De alguna manera están relacionados con C ++ const. Un objeto en C ++ no se modificará a través de un constpuntero a menos que se constelimine 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.
Laurence Gonsalves
16

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.

KeithB
fuente
1
1 plantilla de especialización es increíblemente importante para metaprogramming en tiempo de compilación - esta diferencia en sí mismo hace genéricos de Java que mucho menos potente
Faisal Vali
13

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í .

texto alternativo
(fuente: oreilly.com )

Julien Chastang
fuente
5

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

OscarRyz
fuente
3

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.

Ferruccio
fuente
3

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 suma Specialobjetos, se llamará al tercero. No creo que esto sea posible con Java.

KeithB
fuente
2
Puede ser porque Java no tiene punteros .. !! ¿Puedes explicar con un mejor ejemplo?
Bhavuk Mathur
2

Lo resumiré en una sola oración: las plantillas crean nuevos tipos, los genéricos restringen los tipos existentes.

Marqués de Lorne
fuente
2
¡Tu explicación es muy breve! Y tiene mucho sentido para las personas que entienden bien el tema. Pero para las personas que aún no lo entienden, no ayuda mucho. (¿Cuál es el caso de alguien que hace una pregunta sobre SO, entendido?)
Jakub
1

@Keith:

Ese código es realmente incorrecto y, aparte de las fallas más pequeñas ( templateomitidas, 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); }
Konrad Rudolph
fuente
2
¿Por qué es esta una respuesta y no un comentario?
Laurence Gonsalves
3
@ Laurence: por una vez, porque se publicó mucho antes de que se implementaran los comentarios en Stack Overflow. Por otro lado, porque no es solo un comentario, también es una respuesta a la pregunta: algo como el código anterior no es posible en Java.
Konrad Rudolph el
1

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 de C ++ pueden usar tipos primitivos, como int. Java no puede y debe usar Integer.
  • En Java, puede restringir los parámetros de tipo de la plantilla para que sean de cierto tipo. Por ejemplo, puede usar genéricos para implementar un CardDeck y especificar que el parámetro de tipo debe extenderse desde CardGame.
  • En C ++, el parámetro tipo se puede instanciar, mientras que Java no lo admite.
  • En Java, el parámetro tipo (es decir, el Foo en MyClass) no se puede usar para métodos y variables estáticas, ya que estos se compartirían entre MyClass y MyClass. En C ++, estas clases son diferentes, por lo que el parámetro tipo se puede usar para métodos y variables estáticos.
  • En Java, todas las instancias de MyClass, independientemente de sus parámetros de tipo, son del mismo tipo. Los parámetros de tipo se borran en tiempo de ejecución. En C ++, las instancias con diferentes parámetros de tipo son diferentes tipos.
Jaycee
fuente
0

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.

MigMit
fuente
Bien, esto tiene 3 años pero de todos modos estoy respondiendo. No entiendo tu punto. Toda la razón por la que Java genera un error para esa línea comentada es porque llamaría a una función que espera dos A con diferentes argumentos (A y Cons <A>) y esto es realmente básico y también ocurre cuando no hay genéricos involucrados. C ++ hace eso también. Aparte de eso, este código me dio cáncer porque es realmente horrible. Sin embargo, todavía lo haría así en C ++, por supuesto, debe modificarlo porque C ++ no es Java, pero eso no es una desventaja de las Plantillas de C ++.
clocktown
@clocktown no, NO PUEDES hacer eso en C ++. Ninguna cantidad de modificaciones permitiría eso. Y eso ES una desventaja de las plantillas C ++.
MigMit
Lo que se suponía que debía hacer su Código, advertir sobre diferentes longitudes, no sirve. En su ejemplo comentado, solo produce errores debido a argumentos no coincidentes. Eso también funciona en C ++. Puede escribir código que sea semánticamente equivalente y mucho mejor que este desastre en C ++ y Java.
clocktown
Lo hace. Los argumentos no coinciden exactamente porque las longitudes son diferentes. No puedes hacer eso en C ++.
MigMit
0

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.

Piyush-Ask Any Difference
fuente