Risposte:
C'è una grande differenza tra loro. In C ++ non è necessario specificare una classe o un'interfaccia per il tipo generico. Ecco perché puoi creare funzioni e classi veramente generiche, con l'avvertenza di una digitazione più libera.
template <typename T> T sum(T a, T b) { return a + b; }
Il metodo sopra aggiunge due oggetti dello stesso tipo e può essere utilizzato per qualsiasi tipo T con l'operatore "+" disponibile.
In Java devi specificare un tipo se vuoi chiamare metodi sugli oggetti passati, qualcosa del tipo:
<T extends Something> T sum(T a, T b) { return a.add ( b ); }
In C ++ le funzioni / classi generiche possono essere definite solo nelle intestazioni, poiché il compilatore genera funzioni diverse per tipi diversi (con cui viene invocato). Quindi la compilazione è più lenta. In Java la compilazione non ha una grossa penalità, ma Java utilizza una tecnica chiamata "cancellazione" in cui il tipo generico viene cancellato in fase di esecuzione, quindi in fase di esecuzione Java sta effettivamente chiamando ...
Something sum(Something a, Something b) { return a.add ( b ); }
Quindi la programmazione generica in Java non è davvero utile, è solo un po 'di zucchero sintattico per aiutare con il nuovo costrutto foreach.
EDIT: l'opinione sopra sull'utilità è stata scritta da un sé più giovane. I generici di Java aiutano ovviamente con la sicurezza dei tipi.
extends
o super
. La risposta è errata,
Java Generics è notevolmente diverso dai modelli C ++.
Fondamentalmente in C ++ i modelli sono fondamentalmente un preprocessore / macro set glorificato ( Nota: poiché alcune persone sembrano incapaci di comprendere un'analogia, non sto dicendo che l'elaborazione del modello sia una macro). In Java sono fondamentalmente zucchero sintattico per ridurre al minimo la fusione di oggetti in caldaia. Ecco un'introduzione abbastanza decente ai modelli C ++ rispetto ai generici Java .
Per approfondire questo punto: quando usi un modello C ++, fondamentalmente stai creando un'altra copia del codice, proprio come se usassi una #define
macro. Questo ti permette di fare cose come avere int
parametri nelle definizioni dei template che determinano le dimensioni degli array e così via.
Java non funziona così. In Java tutti gli oggetti si estendono da java.lang.Object così, pre-generici, dovresti scrivere codice in questo modo:
public class PhoneNumbers {
private Map phoneNumbers = new HashMap();
public String getPhoneNumber(String name) {
return (String)phoneNumbers.get(name);
}
...
}
perché tutti i tipi di raccolta Java utilizzavano Object come tipo di base in modo da poter inserire qualsiasi cosa in essi. Java 5 gira e aggiunge generici in modo da poter fare cose come:
public class PhoneNumbers {
private Map<String, String> phoneNumbers = new HashMap<String, String>();
public String getPhoneNumber(String name) {
return phoneNumbers.get(name);
}
...
}
E questo è tutto Java Generics: involucri per il casting di oggetti. Questo perché Java Generics non è raffinato. Usano la cancellazione del tipo. Questa decisione è stata presa perché Java Generics è arrivato così tardi nel pezzo che non volevano interrompere la retrocompatibilità (a Map<String, String>
è utilizzabile ogni volta che Map
viene richiesto). Confronta questo con .Net / C # dove non viene utilizzata la cancellazione del tipo, il che porta a tutti i tipi di differenze (ad esempio puoi usare tipi primitivi IEnumerable
e IEnumerable<T>
non avere alcuna relazione l'uno con l'altro).
E una classe che usa generici compilati con un compilatore Java 5+ è utilizzabile su JDK 1.4 (supponendo che non utilizzi altre funzionalità o classi che richiedono Java 5+).
Ecco perché Java Generics è chiamato zucchero sintattico .
Ma questa decisione su come fare i generici ha effetti profondi così tanto che le (superbe) FAQ di Java Generics sono sorte per rispondere alle molte, molte domande che le persone hanno su Java Generics.
I modelli C ++ hanno una serie di funzionalità che Java Generics non ha:
Uso di argomenti di tipo primitivo.
Per esempio:
template<class T, int i>
class Matrix {
int T[i][i];
...
}
Java non consente l'uso di argomenti di tipo primitivo in generici.
Uso di argomenti di tipo predefiniti , che è una caratteristica che mi manca in Java ma ci sono ragioni di compatibilità all'indietro per questo;
Per esempio:
public class ObservableList<T extends List> {
...
}
È davvero necessario sottolineare che le invocazioni di modelli con argomenti diversi sono in realtà tipi diversi. Non condividono nemmeno i membri statici. In Java non è così.
A parte le differenze con i generici, per completezza, ecco un confronto di base tra C ++ e Java (e un altro ).
E posso anche suggerire di pensare in Java . Come programmatore C ++ molti concetti come oggetti saranno già di seconda natura ma ci sono sottili differenze, quindi può valere la pena avere un testo introduttivo anche se si sfogliano parti.
Molto di ciò che imparerai imparando Java sono tutte le librerie (entrambe standard - ciò che viene fornito nel JDK - e non standard, che include cose comunemente usate come Spring). La sintassi Java è più dettagliata della sintassi C ++ e non ha molte funzionalità C ++ (ad es. Sovraccarico dell'operatore, ereditarietà multipla, meccanismo di distruttore, ecc.) Ma ciò non lo rende nemmeno un sottoinsieme di C ++.
Map map = new HashMap<String, String>
. Significa che puoi distribuire un nuovo codice su una vecchia JVM e verrà eseguito a causa delle somiglianze nel bytecode.
C ++ ha modelli. Java ha generici, che assomigliano in qualche modo ai modelli C ++, ma sono molto, molto diversi.
I modelli funzionano, come suggerisce il nome, fornendo al compilatore un modello (aspetta ...) che può usare per generare un codice sicuro compilando i parametri del modello.
I generici, a quanto ho capito, funzionano al contrario: i parametri di tipo vengono utilizzati dal compilatore per verificare che il codice che li utilizza sia sicuro per i tipi, ma il codice risultante viene generato senza tipi.
Pensa ai modelli C ++ come a un ottimo sistema macro e ai generici Java come uno strumento per generare automaticamente i tipografi.
const
. Un oggetto in C ++ non verrà modificato tramite un const
puntatore a meno che il const
-ness non venga eliminato. Allo stesso modo, i cast impliciti creati da tipi generici in Java sono garantiti come "sicuri" a meno che i parametri del tipo non vengano eliminati manualmente da qualche parte nel codice.
Un'altra caratteristica dei modelli C ++ che i generici Java non hanno è la specializzazione. Ciò ti consente di avere un'implementazione diversa per tipi specifici. Quindi, ad esempio, puoi avere una versione altamente ottimizzata per un int , pur avendo una versione generica per il resto dei tipi. Oppure puoi avere versioni diverse per tipi di puntatore e non puntatore. Ciò è utile se si desidera operare sull'oggetto con riferimenti quando si passa un puntatore.
C'è una grande spiegazione di questo argomento in Java Generics and Collections di Maurice Naftalin, Philip Wadler. Consiglio vivamente questo libro. Per citare:
I generici in Java assomigliano ai modelli in C ++. ... La sintassi è volutamente simile e la semantica è deliberatamente diversa. ... Semanticamente, i generici Java sono definiti dalla cancellazione, mentre i modelli C ++ sono definiti dall'espansione.
Si prega di leggere la spiegazione completa qui .
(fonte: oreilly.com )
Fondamentalmente, i modelli AFAIK, C ++ creano una copia del codice per ogni tipo, mentre i generici Java usano esattamente lo stesso codice.
Sì, puoi dire che il modello C ++ è equivalente al concetto generico Java (anche se più correttamente sarebbe dire che i generici Java sono equivalenti al concetto C ++)
Se hai familiarità con il meccanismo del modello di C ++, potresti pensare che i generici siano simili, ma la somiglianza è superficiale. I generici non generano una nuova classe per ogni specializzazione, né consentono la "metaprogrammazione dei modelli".
da: Java Generics
I generici Java (e C #) sembrano essere un semplice meccanismo di sostituzione del tipo di runtime.
I modelli C ++ sono un costrutto in fase di compilazione che ti offre un modo per modificare il linguaggio in base alle tue esigenze. Sono in realtà un linguaggio puramente funzionale che il compilatore esegue durante una compilazione.
Un altro vantaggio dei modelli C ++ è la specializzazione.
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); }
Ora, se si chiama sum con puntatori, verrà chiamato il secondo metodo, se si chiama sum con oggetti non puntatore verrà chiamato il primo metodo e se si chiama sum
con Special
oggetti, verrà chiamato il terzo. Non penso che questo sia possibile con Java.
Lo riassumo in una sola frase: i modelli creano nuovi tipi, i generici limitano i tipi esistenti.
@Keith:
Tale codice è in realtà errato e, a parte i piccoli problemi ( template
omessa, la sintassi della specializzazione ha un aspetto diverso), la specializzazione parziale non funziona sui modelli di funzione, ma solo sui modelli di classe. Il codice funzionerebbe comunque senza una specializzazione parziale del template, usando invece un vecchio sovraccarico normale:
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 risposta che segue è dal libro Cracking The Coding Interview Solutions al capitolo 13, che penso sia molto buono.
L'implementazione dei generici Java si basa su un'idea di "cancellazione del tipo:" Questa tecnica elimina i tipi parametrizzati quando il codice sorgente viene tradotto nel bytecode JVM (Java Virtual Machine). Ad esempio, supponiamo di avere il codice Java di seguito:
Vector<String> vector = new Vector<String>();
vector.add(new String("hello"));
String str = vector.get(0);
Durante la compilazione, questo codice viene riscritto in:
Vector vector = new Vector();
vector.add(new String("hello"));
String str = (String) vector.get(0);
L'uso dei generici Java non ha cambiato molto le nostre capacità; ha reso le cose un po 'più belle. Per questo motivo, i generici Java vengono talvolta chiamati "zucchero sintattico:".
Questo è abbastanza diverso dal C ++. In C ++, i modelli sono essenzialmente un set di macro glorificato, con il compilatore che crea una nuova copia del codice modello per ciascun tipo. Ne è una prova il fatto che un'istanza di MyClass non condividerà una variabile statica con MyClass. Le istanze di traino di MyClass, tuttavia, condivideranno una variabile statica.
/*** 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, le variabili statiche sono condivise tra istanze di MyClass, indipendentemente dai diversi parametri di tipo.
I modelli generici Java e C ++ presentano numerose altre differenze. Questi includono:
I modelli non sono altro che un sistema macro. Sintassi zucchero. Vengono completamente espansi prima della compilazione effettiva (o, almeno, i compilatori si comportano come se fosse il caso).
Esempio:
Diciamo che vogliamo due funzioni. Una funzione prende due sequenze (elenco, matrici, vettori, qualunque cosa vada) di numeri e restituisce il loro prodotto interno. Un'altra funzione richiede una lunghezza, genera due sequenze di quella lunghezza, le passa alla prima funzione e restituisce il risultato. Il problema è che potremmo commettere un errore nella seconda funzione, in modo che queste due funzioni non siano realmente della stessa lunghezza. Abbiamo bisogno del compilatore per avvisarci in questo caso. Non quando il programma è in esecuzione, ma durante la compilazione.
In Java puoi fare qualcosa del genere:
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 # puoi scrivere quasi la stessa cosa. Prova a riscriverlo in C ++ e non verrà compilato, lamentandoti dell'infinita espansione dei modelli.
Vorrei citare askanydifference qui:
La differenza principale tra C ++ e Java risiede nella loro dipendenza dalla piattaforma. Mentre C ++ è un linguaggio dipendente dalla piattaforma, Java è un linguaggio indipendente dalla piattaforma.
L'istruzione precedente è il motivo per cui C ++ è in grado di fornire veri tipi generici. Mentre Java ha un controllo rigoroso e quindi non consente l'uso di generici come C ++ lo consente.