Was sind die Unterschiede zwischen "generischen" Typen in C ++ und Java?


Antworten:


144

Es gibt einen großen Unterschied zwischen ihnen. In C ++ müssen Sie keine Klasse oder Schnittstelle für den generischen Typ angeben. Aus diesem Grund können Sie wirklich generische Funktionen und Klassen mit der Einschränkung einer lockeren Eingabe erstellen.

template <typename T> T sum(T a, T b) { return a + b; }

Die obige Methode fügt zwei Objekte desselben Typs hinzu und kann für jeden Typ T verwendet werden, für den der Operator "+" verfügbar ist.

In Java müssen Sie einen Typ angeben, wenn Sie Methoden für die übergebenen Objekte aufrufen möchten, z.

<T extends Something> T sum(T a, T b) { return a.add ( b ); }

In C ++ können generische Funktionen / Klassen nur in Headern definiert werden, da der Compiler verschiedene Funktionen für verschiedene Typen generiert (mit denen er aufgerufen wird). Die Kompilierung ist also langsamer. In Java hat die Kompilierung keine großen Nachteile, aber Java verwendet eine Technik namens "Löschen", bei der der generische Typ zur Laufzeit gelöscht wird, sodass Java zur Laufzeit tatsächlich ...

Something sum(Something a, Something b) { return a.add ( b ); }

Generische Programmierung in Java ist also nicht wirklich nützlich, es ist nur ein wenig syntaktischer Zucker, um mit dem neuen foreach-Konstrukt zu helfen.

EDIT: Die obige Meinung zur Nützlichkeit wurde von einem jüngeren Ich geschrieben. Die Generika von Java helfen natürlich bei der Typensicherheit.


27
Er hat vollkommen recht, dass es sich nur um einen ausgeklügelten syntaktischen Zucker handelt.
Alphazero

31
Es ist kein rein syntaktischer Zucker. Der Compiler verwendet diese Informationen zum Überprüfen von Typen. Obwohl die Informationen zur Laufzeit nicht verfügbar sind, würde ich etwas, das kompiliert wird, nicht einfach "syntaktischen Zucker" nennen. Wenn Sie es so nennen würden, dann ist C nur syntaktischer Zucker für die Montage, und das ist nur syntaktischer Zucker für Maschinencode :)
dtech

42
Ich denke, syntaktischer Zucker ist nützlich.
Poitroae

5
Sie haben einen wichtigen Unterschied übersehen, mit dem Sie ein Generikum instanziieren können. In c ++ ist es möglich, die Vorlage <int N> zu verwenden und für jede Zahl, die zur Instanziierung verwendet wird, ein anderes Ergebnis zu erhalten. Es wird für das Meta-Progaming zur Kompilierungszeit verwendet. Wie die Antwort in: stackoverflow.com/questions/189172/c-templates-turing-complete
stonemetal

2
Sie müssen keinen Typ in Form von extendsoder angeben super. Antwort ist falsch,
Marquis von Lorne

124

Java Generics sind massiv unterschiedlich zu C ++ Vorlagen.

Grundsätzlich handelt es sich bei C ++ - Vorlagen im Grunde genommen um einen verherrlichten Präprozessor- / Makrosatz ( Hinweis: Da einige Leute eine Analogie nicht verstehen können, sage ich nicht, dass die Vorlagenverarbeitung ein Makro ist). In Java handelt es sich im Grunde genommen um syntaktischen Zucker, um das Boilerplate-Casting von Objekten zu minimieren. Hier ist eine ziemlich anständige Einführung in C ++ - Vorlagen im Vergleich zu Java-Generika .

Um auf diesen Punkt näher einzugehen: Wenn Sie eine C ++ - Vorlage verwenden, erstellen Sie im Grunde eine weitere Kopie des Codes, als ob Sie ein #defineMakro verwendet hätten. Auf diese Weise können Sie beispielsweise intParameter in Vorlagendefinitionen festlegen, die die Größe von Arrays usw. bestimmen.

Java funktioniert so nicht. In Java erstrecken sich alle Objekte von java.lang.Object . Vor Generics würden Sie also Code wie folgt schreiben:

public class PhoneNumbers {
  private Map phoneNumbers = new HashMap();

  public String getPhoneNumber(String name) {
    return (String)phoneNumbers.get(name);
  }

  ...
}

weil alle Java-Sammlungstypen Object als Basistyp verwendeten, sodass Sie alles in sie einfügen konnten. Java 5 rollt herum und fügt Generika hinzu, damit Sie Dinge tun können wie:

public class PhoneNumbers {
  private Map<String, String> phoneNumbers = new HashMap<String, String>();

  public String getPhoneNumber(String name) {
    return phoneNumbers.get(name);
  }

  ...
}

Und das ist alles, was Java Generics sind: Wrapper zum Casting von Objekten. Das liegt daran, dass Java Generics nicht verfeinert werden. Sie verwenden Typlöschung. Diese Entscheidung wurde getroffen, weil Java Generics so spät in den Artikel kam, dass sie die Abwärtskompatibilität nicht aufheben wollten (a Map<String, String>ist immer dann verwendbar, wenn a benötigt Mapwird). Vergleichen Sie dies mit .Net / C #, wo keine Typlöschung verwendet wird, was zu allen möglichen Unterschieden führt (z. B. können Sie primitive Typen verwenden IEnumerableund IEnumerable<T>haben keine Beziehung zueinander).

Eine Klasse mit Generika, die mit einem Java 5+ -Compiler kompiliert wurden, kann unter JDK 1.4 verwendet werden (vorausgesetzt, sie verwendet keine anderen Funktionen oder Klassen, für die Java 5+ erforderlich ist).

Deshalb werden Java-Generika als syntaktischer Zucker bezeichnet .

Diese Entscheidung über die Erstellung von Generika hat jedoch so tiefgreifende Auswirkungen, dass die (hervorragenden) häufig gestellten Fragen zu Java-Generika aufgetaucht sind, um die vielen, vielen Fragen zu beantworten, die Menschen zu Java-Generika haben.

C ++ - Vorlagen bieten eine Reihe von Funktionen, die Java Generics nicht bietet:

  • Verwendung primitiver Typargumente.

    Beispielsweise:

    template<class T, int i>
    class Matrix {
      int T[i][i];
      ...
    }

    Java erlaubt nicht die Verwendung primitiver Typargumente in Generika.

  • Verwendung von Standardargumenten , eine Funktion, die ich in Java vermisse, für die es jedoch Gründe für die Abwärtskompatibilität gibt.

  • Java ermöglicht die Begrenzung von Argumenten.

Beispielsweise:

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

Es muss wirklich betont werden, dass Vorlagenaufrufe mit unterschiedlichen Argumenten wirklich unterschiedliche Typen sind. Sie teilen nicht einmal statische Mitglieder. In Java ist dies nicht der Fall.

Abgesehen von den Unterschieden zu Generika ist hier der Vollständigkeit halber ein grundlegender Vergleich von C ++ und Java (und einem anderen ) aufgeführt.

Und ich kann auch vorschlagen, in Java zu denken . Als C ++ - Programmierer sind viele Konzepte wie Objekte bereits selbstverständlich, aber es gibt subtile Unterschiede, sodass es sich lohnen kann, einen Einführungstext zu haben, selbst wenn Sie Teile überfliegen.

Vieles, was Sie beim Erlernen von Java lernen werden, sind alle Bibliotheken (sowohl Standard - was im JDK enthalten ist - als auch Nicht-Standard, einschließlich häufig verwendeter Dinge wie Spring). Die Java-Syntax ist ausführlicher als die C ++ - Syntax und verfügt nicht über viele C ++ - Funktionen (z. B. Überladen von Operatoren, Mehrfachvererbung, Destruktormechanismus usw.), macht sie jedoch auch nicht unbedingt zu einer Teilmenge von C ++.


1
Sie sind im Konzept nicht gleichwertig. Das beste Beispiel ist das merkwürdig wiederkehrende Vorlagenmuster. Das zweitbeste ist politikorientiertes Design. Das drittbeste ist die Tatsache, dass in C ++ ganzzahlige Zahlen in spitzen Klammern (myArray <5>) übergeben werden können.
Max Lybbert

1
Nein, sie sind konzeptionell nicht gleichwertig. Es gibt einige Überschneidungen im Konzept, aber nicht viel. In beiden Fällen können Sie List <T> erstellen, aber das ist ungefähr so ​​weit. C ++ - Vorlagen gehen viel weiter.
Jalf

5
Es ist wichtig zu beachten, dass das Problem der Typlöschung mehr bedeutet als nur Abwärtskompatibilität für Map map = new HashMap<String, String>. Dies bedeutet, dass Sie neuen Code auf einer alten JVM bereitstellen können und dieser aufgrund der Ähnlichkeiten im Bytecode ausgeführt wird.
Yuval Adam

1
Sie werden feststellen, dass ich sagte "im Grunde ein verherrlichter Präprozessor / Makro". Dies war eine Analogie, da mit jeder Vorlagendeklaration mehr Code erstellt wird (im Gegensatz zu Java / C #).
Cletus

4
Template - Code ist sehr anders als Copy-and-Paste. Wenn Sie in Bezug auf die Makroerweiterung
Nemanja Trifunovic

86

C ++ hat Vorlagen. Java hat Generika, die ein bisschen wie C ++ - Vorlagen aussehen, aber sie sind sehr, sehr unterschiedlich.

Vorlagen funktionieren, wie der Name schon sagt, indem sie dem Compiler eine (warten Sie ...) Vorlage zur Verfügung stellen, mit der er typsicheren Code durch Ausfüllen der Vorlagenparameter generieren kann.

Generics funktionieren, so wie ich sie verstehe, umgekehrt: Die Typparameter werden vom Compiler verwendet, um zu überprüfen, ob der Code, der sie verwendet, typsicher ist, aber der resultierende Code wird überhaupt ohne Typen generiert.

Stellen Sie sich C ++ - Vorlagen als ein wirklich gutes Makrosystem vor und Java-Generika als Werkzeug zum automatischen Generieren von Typecasts.

 


4
Dies ist eine ziemlich gute, prägnante Erklärung. Eine Optimierung, die ich in Versuchung führen möchte, ist, dass Java-Generika ein Tool zum automatischen Generieren von Typecasts sind, die (unter bestimmten Bedingungen) garantiert sicher sind . In gewisser Weise sind sie mit C ++ verwandt const. Ein Objekt in C ++ wird nicht durch einen constZeiger geändert, es sei denn, die const-ness wird weggeworfen. Ebenso ist garantiert, dass die impliziten Casts, die von generischen Typen in Java erstellt werden, "sicher" sind, es sei denn, die Typparameter werden manuell irgendwo im Code weggeworfen.
Laurence Gonsalves

16

Eine weitere Funktion von C ++ - Vorlagen, die Java-Generika nicht bieten, ist die Spezialisierung. Dies ermöglicht Ihnen eine andere Implementierung für bestimmte Typen. So können Sie beispielsweise eine hochoptimierte Version für einen int haben , während Sie für den Rest der Typen immer noch eine generische Version haben. Oder Sie können verschiedene Versionen für Zeiger- und Nichtzeigertypen verwenden. Dies ist praktisch, wenn Sie das dereferenzierte Objekt bearbeiten möchten, wenn Sie einen Zeiger übergeben.


1
+1 Template-Spezialisierung ist unglaublich wichtig für die Metaprogrammierung zur Kompilierungszeit - dieser Unterschied an sich macht Java-Generika viel weniger wirksam
Faisal Vali

13

Es gibt eine großartige Erklärung zu diesem Thema in Java Generics and Collections Von Maurice Naftalin, Philip Wadler. Ich kann dieses Buch nur empfehlen. Zitieren:

Generika in Java ähneln Vorlagen in C ++. ... Die Syntax ist bewusst ähnlich und die Semantik ist bewusst unterschiedlich. ... Semantisch werden Java-Generika durch Löschen definiert, während C ++ - Vorlagen durch Erweiterung definiert werden.

Bitte lesen Sie die vollständige Erklärung hier .

Alt-Text
(Quelle: oreilly.com )


5

Grundsätzlich erstellen AFAIK, C ++ - Vorlagen eine Kopie des Codes für jeden Typ, während Java-Generika genau denselben Code verwenden.

Ja, Sie können sagen, dass die C ++ - Vorlage dem generischen Java- Konzept entspricht (obwohl es besser wäre zu sagen, dass Java-Generika dem C ++ - Konzept entsprechen).

Wenn Sie mit dem Vorlagenmechanismus von C ++ vertraut sind, denken Sie vielleicht, dass Generika ähnlich sind, aber die Ähnlichkeit ist oberflächlich. Generika generieren weder für jede Spezialisierung eine neue Klasse, noch erlauben sie eine "Vorlagen-Metaprogrammierung".

von: Java Generics


3

Java- (und C #) - Generika scheinen ein einfacher Substitutionsmechanismus für Laufzeitarten zu sein.
C ++ - Vorlagen sind ein Konstrukt zur Kompilierungszeit, mit dem Sie die Sprache an Ihre Anforderungen anpassen können. Sie sind eigentlich eine rein funktionale Sprache, die der Compiler während einer Kompilierung ausführt.


3

Ein weiterer Vorteil von C ++ - Vorlagen ist die Spezialisierung.

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); }

Wenn Sie nun sum mit Zeigern aufrufen, wird die zweite Methode aufgerufen, wenn Sie sum mit Nicht-Zeigerobjekten aufrufen, wird die erste Methode aufgerufen, und wenn Sie summit SpecialObjekten aufrufen , wird die dritte aufgerufen. Ich denke nicht, dass dies mit Java möglich ist.


2
Kann sein, weil Java keine Zeiger hat .. !! können Sie mit einem besseren Beispiel erklären?
Bhavuk Mathur

2

Ich werde es in einem einzigen Satz zusammenfassen: Vorlagen erstellen neue Typen, Generika schränken vorhandene Typen ein.


2
Ihre Erklärung ist so kurz! Und macht Sinn für Leute, die das Thema gut verstehen. Aber für Leute, die es noch nicht verstehen, hilft es nicht viel. (Was ist der Fall, wenn jemand eine Frage zu SO stellt, verstanden?)
Jakub

1

@ Keith:

Dieser Code ist tatsächlich falsch und abgesehen von den kleineren Störungen ( templateweggelassen, Spezialisierungssyntax sieht anders aus) funktioniert die Teilspezialisierung nicht für Funktionsvorlagen, sondern nur für Klassenvorlagen. Der Code würde jedoch ohne teilweise Vorlagenspezialisierung funktionieren und stattdessen eine einfache alte Überladung verwenden:

template <typename T> T sum(T a, T b) { return a + b; }
template <typename T> T sum(T* a, T* b) { return (*a) + (*b); }

2
Warum ist das eine Antwort und kein Kommentar?
Laurence Gonsalves

3
@Laurence: ausnahmsweise, weil es lange vor der Implementierung von Kommentaren zu Stack Overflow veröffentlicht wurde. Zum anderen, weil es nicht nur ein Kommentar ist, sondern auch eine Antwort auf die Frage: So etwas wie der obige Code ist in Java nicht möglich.
Konrad Rudolph

1

Die Antwort unten stammt aus dem Buch Cracking The Coding Interview Solutions zu Kapitel 13, das ich für sehr gut halte.

Die Implementierung von Java-Generika basiert auf der Idee des "Löschens von Typen": Diese Technik eliminiert die parametrisierten Typen, wenn der Quellcode in den Bytecode der Java Virtual Machine (JVM) übersetzt wird. Angenommen, Sie haben den folgenden Java-Code:

Vector<String> vector = new Vector<String>();
vector.add(new String("hello"));
String str = vector.get(0);

Während der Kompilierung wird dieser Code neu geschrieben in:

Vector vector = new Vector();
vector.add(new String("hello"));
String str = (String) vector.get(0);

Die Verwendung von Java-Generika hat an unseren Funktionen nicht viel geändert. es machte die Dinge nur ein bisschen schöner. Aus diesem Grund werden Java-Generika manchmal als "syntaktischer Zucker:" bezeichnet.

Dies ist ganz anders als in C ++. In C ++ sind Vorlagen im Wesentlichen ein verherrlichter Makrosatz, wobei der Compiler für jeden Typ eine neue Kopie des Vorlagencodes erstellt. Ein Beweis dafür ist die Tatsache, dass eine Instanz von MyClass keine statische Variable mit MyClass teilt. Zwei Instanzen von MyClass teilen sich jedoch eine statische Variable.

/*** 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

In Java werden statische Variablen unabhängig von den verschiedenen Typparametern von Instanzen von MyClass gemeinsam genutzt.

Java-Generika und C ++ - Vorlagen weisen eine Reihe weiterer Unterschiede auf. Diese beinhalten:

  • C ++ - Vorlagen können primitive Typen wie int verwenden. Java kann und muss stattdessen Integer verwenden.
  • In Java können Sie die Typparameter der Vorlage auf einen bestimmten Typ beschränken. Beispielsweise können Sie Generika verwenden, um ein CardDeck zu implementieren und anzugeben, dass der Typparameter von CardGame aus erweitert werden muss.
  • In C ++ kann der Typparameter instanziiert werden, während Java dies nicht unterstützt.
  • In Java kann der Typparameter (dh das Foo in MyClass) nicht für statische Methoden und Variablen verwendet werden, da diese von MyClass und MyClass gemeinsam genutzt werden. In C ++ unterscheiden sich diese Klassen, sodass der Typparameter für statische Methoden und Variablen verwendet werden kann.
  • In Java sind alle Instanzen von MyClass unabhängig von ihren Typparametern vom gleichen Typ. Die Typparameter werden zur Laufzeit gelöscht. In C ++ sind Instanzen mit unterschiedlichen Typparametern unterschiedliche Typen.

0

Vorlagen sind nichts anderes als ein Makrosystem. Syntax Zucker. Sie werden vor der eigentlichen Kompilierung vollständig erweitert (oder zumindest verhalten sich Compiler so, als ob dies der Fall wäre).

Beispiel:

Nehmen wir an, wir wollen zwei Funktionen. Eine Funktion verwendet zwei Sequenzen (Liste, Arrays, Vektoren, was auch immer) von Zahlen und gibt ihr inneres Produkt zurück. Eine andere Funktion nimmt eine Länge an, generiert zwei Sequenzen dieser Länge, übergibt sie an die erste Funktion und gibt das Ergebnis zurück. Der Haken ist, dass wir in der zweiten Funktion einen Fehler machen könnten, so dass diese beiden Funktionen nicht wirklich gleich lang sind. In diesem Fall muss der Compiler uns warnen. Nicht wenn das Programm läuft, sondern wenn es kompiliert wird.

In Java können Sie so etwas tun:

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");
        }
    }
}

In C # können Sie fast dasselbe schreiben. Versuchen Sie, es in C ++ neu zu schreiben, und es wird nicht kompiliert. Sie beschweren sich über die unendliche Erweiterung von Vorlagen.


Okay, das ist 3 Jahre alt, aber ich antworte trotzdem. Ich verstehe deinen Standpunkt nicht. Der ganze Grund, warum Java einen Fehler für diese kommentierte Zeile generiert, ist, dass Sie eine Funktion aufrufen würden, die zwei A mit unterschiedlichen Argumenten (A und Cons <A>) erwartet. Dies ist wirklich grundlegend und geschieht auch, wenn keine Generika beteiligt sind. C ++ macht das auch. Abgesehen davon gab mir dieser Code Krebs, weil er wirklich schrecklich ist. Allerdings würden Sie es in C ++ immer noch so machen, Sie müssen dies natürlich ändern, da C ++ nicht Java ist, aber das ist kein Nachteil der C ++ - Vorlagen.
Clocktown

@clocktown nein, das kannst du in C ++ NICHT. Keine Änderung würde dies zulassen. Und das ist ein Nachteil von C ++ - Vorlagen.
MigMit

Was Ihr Code tun sollte - vor unterschiedlicher Länge warnen - tut es nicht. In Ihrem auskommentierten Beispiel werden nur Fehler aufgrund nicht übereinstimmender Argumente erzeugt. Das funktioniert auch in C ++. Sie können Code eingeben, der semantisch äquivalent und weitaus besser als dieses Durcheinander in C ++ und Java ist.
Clocktown

Es tut. Argumente stimmen nicht genau überein, da die Längen unterschiedlich sind. Das geht in C ++ nicht.
MigMit

0

Ich möchte hier askanydifference zitieren :

Der Hauptunterschied zwischen C ++ und Java liegt in ihrer Abhängigkeit von der Plattform. Während C ++ eine plattformabhängige Sprache ist, ist Java eine plattformunabhängige Sprache.

Die obige Anweisung ist der Grund, warum C ++ echte generische Typen bereitstellen kann. Java hat zwar strenge Überprüfungen und erlaubt daher nicht, Generika so zu verwenden, wie es C ++ erlaubt.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.