Pregunta

Java tiene genéricos y C++ proporciona un modelo de programación muy sólido con templates.Entonces, ¿cuál es la diferencia entre los genéricos de C++ y Java?

¿Fue útil?

Solución

Hay una gran diferencia entre ellos.En C++ no es necesario especificar una clase o una interfaz para el tipo genérico.Es por eso que puedes crear funciones y clases verdaderamente genéricas, con la salvedad 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 el operador "+" disponible.

En Java tienes que especificar un tipo si quieres 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 usa 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 ); }

Así que la programación genérica en Java no es realmente útil, es sólo un poco de azúcar sintáctico 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.

Otros consejos

Los genéricos de Java son macizamente diferente a las plantillas de C++.

Básicamente, en C++ las plantillas son básicamente un preprocesador/conjunto de macros 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áctico para minimizar la conversión repetitiva de objetos.Aquí hay un bastante decente. introducción a las plantillas de C++ frente a los genéricos de Java.

Para profundizar en este punto:Cuando usas una plantilla de C++, básicamente estás creando otra copia del código, como si usaras una #define macro.Esto le permite hacer cosas como tener int parámetros en definiciones de plantillas que determinan los tamaños de matrices y demás.

Java no funciona así.En Java todos los objetos se extienden desde java.lang.Objeto entonces, antes de los genéricos, 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 colecciones de Java usaban Objeto como tipo base, por lo que podías poner cualquier cosa en ellos.Java 5 avanza y agrega genéricos para que puedas 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 genéricos de Java:Envoltorios para fundir objetos.Esto se debe a que los genéricos de Java no están refinados.Usan borrado de tipo.Esta decisión se tomó porque Java Generics apareció tan tarde en el artículo que no querían romper la compatibilidad con versiones anteriores (un Map<String, String> es utilizable siempre que un Map es necesario).Compare esto con .Net/C#, donde no se utiliza el borrado de tipos, lo que genera todo tipo de diferencias (p. ej.puedes usar tipos primitivos y IEnumerable y IEnumerable<T> no guardan relación entre sí).

Y una clase que utiliza genéricos compilados con un compilador Java 5+ se puede utilizar en JDK 1.4 (suponiendo que no utilice ninguna otra característica o clase que requiera Java 5+).

Por eso los genéricos de Java se llaman azúcar sintáctica.

Pero esta decisión sobre cómo hacer genéricos tiene efectos tan profundos que el (magnífico) Preguntas frecuentes sobre genéricos de Java ha surgido para responder a las muchas, muchas preguntas que la gente tiene sobre Java Generics.

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 predeterminado, que es una característica que extraño en Java, pero existen razones de compatibilidad con versiones anteriores para ello;

  • Java permite delimitar argumentos.

Por ejemplo:

public class ObservableList<T extends List> {
  ...
}

Realmente es necesario enfatizar que las invocaciones de plantillas con diferentes argumentos en realidad son tipos diferentes.Ni siquiera comparten miembros estáticos.En Java este no es el caso.

Aparte de las diferencias con los genéricos, para completar, aquí hay una comparación básica de C ++ y Java (y otro).

Y también puedo sugerir Pensando en Java.Como programador de C++, muchos de los conceptos, como los objetos, ya serán algo natural, pero existen diferencias sutiles, por lo que puede valer la pena tener un texto introductorio incluso si hojeas algunas 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++.

C++ tiene plantillas.Java tiene genéricos, que se parecen un poco a las plantillas de C++, pero son muy, muy diferentes.

Las plantillas funcionan, como su nombre lo indica, proporcionando al compilador una (espérelo...) plantilla que puede usar para generar código con seguridad de tipos completando los parámetros de la plantilla.

Los genéricos, según tengo entendido, funcionan al revés:El compilador utiliza los parámetros de tipo para verificar que el código que los utiliza sea seguro para los tipos, pero el código resultante se genera sin ningún tipo.

Piense en las plantillas de C++ como realmente bueno sistema de macros y genéricos de Java como herramienta para generar encasillamientos automáticamente.

 

Otra característica que tienen las plantillas de C++ y que no tienen los genéricos de Java es la especialización.Eso le permite tener una implementación diferente para tipos específicos.Así, por ejemplo, podrá disponer de una versión altamente optimizada para un En t, aunque sigue teniendo una versión genérica para el resto de tipos.O puede tener diferentes versiones para tipos con y sin puntero.Esto resulta útil si desea operar en el objeto desreferenciado cuando se le entrega un puntero.

Hay una gran explicación de este tema en Genéricos y colecciones de JavaPor Maurice Naftalin y 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 Java se definen por borrado, donde como las plantillas de C ++ se definen por expansión.

Por favor lea la explicación completa. aquí.

alt text
(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í tú puedo decir esa plantilla de C++ es equivalente a la genérica de Java concepto (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 plantillas 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: Genéricos de Java

Los genéricos de Java (y C#) parecen ser un mecanismo simple de sustitución de tipos 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 adaptarlo a 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 llamas a sum con punteros, se llamará al segundo método, si llamas a sum con objetos que no son punteros, se llamará al primer método, y si llamas sum con Special objetos, se llamará el tercero.No creo que esto sea posible con Java.

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

@Keith:

Ese código es realmente incorrecto y, aparte de los fallos más pequeños (template omitido, la sintaxis de especialización se ve diferente), especialización parcial no trabaje en plantillas de funciones, solo en plantillas de clases.Sin embargo, el código funcionaría sin una especialización parcial de la plantilla, sino que utilizaría la 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. Descifrando la entrevista de codificación Soluciones al Capítulo 13, que me parece muy bueno.

La implementación de los genéricos de Java se basa en la idea de "borrado de tipos": 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, supongamos que tiene el siguiente código Java:

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 nuestras capacidades;simplemente 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, en el que el compilador crea una nueva copia del código de la plantilla para cada tipo.Prueba de esto es el hecho de que una instancia de MyClass no compartirá una variable estática con MyClass.Sin embargo, dos instancias 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.Éstas incluyen:

  • Las plantillas de C++ pueden usar tipos primitivos, como int.Java no puede y en su lugar debe usar entero.
  • En Java, puede restringir los parámetros de tipo de plantilla para que sean de cierto tipo.Por ejemplo, puede usar genéricos para implementar una CardDeck y especificar que el parámetro de tipo debe extenderse desde Cardgame.
  • En C ++, el parámetro de tipo se puede instanciar, mientras que Java no admite esto.
  • En Java, el parámetro de 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 de tipo se puede utilizar 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 tipos diferentes.

Las plantillas no son más que un sistema macro.Azúcar de sintaxis.Se expanden completamente antes de la compilación real (o, al menos, los compiladores se comportan como si así fuera).

Ejemplo:

Digamos que queremos dos funciones.Una función toma dos secuencias (lista, matrices, vectores, lo que sea) 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, de modo que estas dos funciones no tengan realmente la misma longitud.Necesitamos que el compilador nos avise 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 la expansión infinita de las plantillas.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top