Respostas:
Há uma grande diferença entre eles. No C ++, você não precisa especificar uma classe ou uma interface para o tipo genérico. É por isso que você pode criar funções e classes verdadeiramente genéricas, com a ressalva de uma digitação mais flexível.
template <typename T> T sum(T a, T b) { return a + b; }
O método acima adiciona dois objetos do mesmo tipo e pode ser usado para qualquer tipo T que tenha o operador "+" disponível.
Em Java, você deve especificar um tipo se quiser chamar métodos nos objetos passados, algo como:
<T extends Something> T sum(T a, T b) { return a.add ( b ); }
No C ++, funções / classes genéricas só podem ser definidas em cabeçalhos, pois o compilador gera funções diferentes para tipos diferentes (com os quais é chamado). Portanto, a compilação é mais lenta. Em Java, a compilação não possui uma penalidade maior, mas o Java usa uma técnica chamada "apagamento", onde o tipo genérico é apagado no tempo de execução, portanto, no tempo de execução, o Java está realmente chamando ...
Something sum(Something a, Something b) { return a.add ( b ); }
Portanto, a programação genérica em Java não é realmente útil, é apenas um pouco de açúcar sintático para ajudar na nova construção foreach.
EDIT: a opinião acima sobre utilidade foi escrita por um eu mais jovem. Os genéricos de Java ajudam na segurança de tipos, é claro.
extends
ou de super
. A resposta está incorreta #
Os Java Generics são massivamente diferentes dos modelos C ++.
Basicamente, nos modelos C ++ há basicamente um conjunto glorificado de pré-processador / macro ( Nota: como algumas pessoas parecem incapazes de compreender uma analogia, não estou dizendo que o processamento de modelos é uma macro). Em Java, eles são basicamente açúcar sintático para minimizar a conversão de objetos em clichês. Aqui está uma introdução bastante decente aos modelos C ++ e aos genéricos Java .
Para elaborar esse ponto: quando você usa um modelo C ++, está basicamente criando outra cópia do código, como se tivesse usado uma #define
macro. Isso permite que você faça coisas como ter int
parâmetros nas definições de modelo que determinam tamanhos de matrizes e outros.
Java não funciona assim. Em Java, todos os objetos se estendem a partir de java.lang.Object , portanto, pré-genéricos, você escreveria um código como este:
public class PhoneNumbers {
private Map phoneNumbers = new HashMap();
public String getPhoneNumber(String name) {
return (String)phoneNumbers.get(name);
}
...
}
porque todos os tipos de coleção Java usavam Object como seu tipo base, para que você pudesse colocar qualquer coisa neles. O Java 5 rola e adiciona genéricos para que você possa fazer coisas como:
public class PhoneNumbers {
private Map<String, String> phoneNumbers = new HashMap<String, String>();
public String getPhoneNumber(String name) {
return phoneNumbers.get(name);
}
...
}
E isso é tudo o que os Java Generics são: invólucros para converter objetos. Isso ocorre porque o Java Generics não é refinado. Eles usam apagamento de tipo. Essa decisão foi tomada porque o Java Generics apareceu tão tarde na peça que eles não queriam quebrar a compatibilidade com versões anteriores (a Map<String, String>
é utilizável sempre que Map
for solicitado). Compare isso com .Net / C # onde o apagamento de tipo não é usado, o que leva a todos os tipos de diferenças (por exemplo, você pode usar tipos primitivos IEnumerable
e IEnumerable<T>
não ter relação um com o outro).
E uma classe usando genéricos compilados com um compilador Java 5+ é utilizável no JDK 1.4 (supondo que ele não use outros recursos ou classes que exijam o Java 5+).
É por isso que os Java Generics são chamados de açúcar sintático .
Mas essa decisão sobre como fazer genéricos tem efeitos profundos, tanto que as (excelentes) Perguntas frequentes sobre Java Generics surgiram para responder às muitas e muitas perguntas que as pessoas têm sobre Java Generics.
Os modelos C ++ têm vários recursos que o Java Generics não possui:
Uso de argumentos do tipo primitivo.
Por exemplo:
template<class T, int i>
class Matrix {
int T[i][i];
...
}
Java não permite o uso de argumentos de tipo primitivo em genéricos.
Uso de argumentos de tipo padrão , que é um recurso que sinto falta em Java, mas existem motivos de compatibilidade com versões anteriores;
Por exemplo:
public class ObservableList<T extends List> {
...
}
Realmente precisa ser enfatizado que as invocações de modelos com argumentos diferentes são realmente tipos diferentes. Eles nem compartilham membros estáticos. Em Java, esse não é o caso.
Além das diferenças com os genéricos, para completar, aqui está uma comparação básica de C ++ e Java (e outra ).
E também posso sugerir Pensando em Java . Como programador de C ++, muitos dos conceitos, como objetos, já serão de segunda natureza, mas existem diferenças sutis; portanto, pode valer a pena ter um texto introdutório, mesmo que você revele partes.
Muito do que você aprenderá ao aprender Java é todas as bibliotecas (tanto padrão - o que vem no JDK) quanto fora do padrão, que inclui coisas comumente usadas como Spring). A sintaxe Java é mais detalhada que a sintaxe C ++ e não possui muitos recursos C ++ (por exemplo, sobrecarga de operador, herança múltipla, mecanismo destruidor etc.), mas isso também não o torna estritamente um subconjunto de C ++.
Map map = new HashMap<String, String>
. Isso significa que você pode implementar um novo código em uma JVM antiga e ela será executada devido às semelhanças no bytecode.
C ++ tem modelos. O Java possui genéricos, que se parecem com modelos C ++, mas são muito, muito diferentes.
Os modelos funcionam, como o nome indica, fornecendo ao compilador um modelo (aguarde ...) que ele pode usar para gerar código de tipo seguro preenchendo os parâmetros do modelo.
Os genéricos, como eu os entendo, funcionam de maneira inversa: os parâmetros de tipo são usados pelo compilador para verificar se o código que os utiliza é seguro, mas o código resultante é gerado sem tipos.
Pense nos modelos C ++ como um bom sistema macro e os Java genéricos como uma ferramenta para gerar automaticamente previsões de tipo.
const
. Um objeto em C ++ não será modificado por meio de um const
ponteiro, a menos que const
-ness seja eliminado. Da mesma forma, as conversões implícitas criadas por tipos genéricos em Java são garantidas como "seguras", a menos que os parâmetros de tipo sejam eliminados manualmente em algum lugar do código.
Outro recurso que os modelos C ++ têm que os genéricos Java não têm é a especialização. Isso permite que você tenha uma implementação diferente para tipos específicos. Assim, você pode, por exemplo, ter uma versão altamente otimizada para um int , enquanto ainda possui uma versão genérica para o restante dos tipos. Ou você pode ter versões diferentes para tipos de ponteiro e não ponteiro. Isso é útil se você deseja operar no objeto não referenciado quando recebe um ponteiro.
Há uma grande explicação para esse tópico em Java Generics and Collections Por Maurice Naftalin, Philip Wadler. Eu recomendo este livro. Citar:
Os genéricos em Java se parecem com modelos em C ++. ... A sintaxe é deliberadamente semelhante e a semântica é deliberadamente diferente. ... Semanticamente, os genéricos Java são definidos por apagamento, enquanto os modelos C ++ são definidos por expansão.
Por favor, leia a explicação completa aqui .
(fonte: oreilly.com )
Basicamente, os modelos AFAIK, C ++ criam uma cópia do código para cada tipo, enquanto os genéricos Java usam exatamente o mesmo código.
Sim, você pode dizer que o modelo C ++ é equivalente ao conceito genérico Java (embora seja mais apropriado dizer que os genéricos Java são equivalentes ao conceito C ++)
Se você conhece o mecanismo de modelo do C ++, pode pensar que os genéricos são semelhantes, mas a semelhança é superficial. Os genéricos não geram uma nova classe para cada especialização, nem permitem a "metaprogramação de modelos".
de: Java Generics
Os genéricos Java (e C #) parecem ser um mecanismo de substituição simples do tipo tempo de execução.
Os modelos C ++ são uma construção em tempo de compilação que permite modificar a linguagem para atender às suas necessidades. Na verdade, eles são uma linguagem puramente funcional que o compilador executa durante uma compilação.
Outra vantagem dos modelos C ++ é a especialização.
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); }
Agora, se você chamar soma com ponteiros, o segundo método será chamado; se você chamar soma com objetos que não são ponteiros, o primeiro método será chamado e, se você chamar sum
com Special
objetos, o terceiro será chamado. Eu não acho que isso seja possível com Java.
Vou resumir em uma única frase: modelos criam novos tipos, genéricos restringe tipos existentes.
@Keith:
Esse código está realmente errado e, além das pequenas falhas ( template
omitidas, a sintaxe da especialização parece diferente), a especialização parcial não funciona em modelos de função, apenas em modelos de classe. No entanto, o código funcionaria sem especialização parcial do modelo, usando sobrecarga antiga simples:
template <typename T> T sum(T a, T b) { return a + b; }
template <typename T> T sum(T* a, T* b) { return (*a) + (*b); }
A resposta abaixo é do livro Cracking The Coding Interview Solutions para o Capítulo 13, que eu acho muito bom.
A implementação dos genéricos Java está enraizada na ideia de "apagamento de tipo: 'Essa técnica elimina os tipos parametrizados quando o código-fonte é convertido no bytecode da Java Virtual Machine (JVM). Por exemplo, suponha que você tenha o código Java abaixo:
Vector<String> vector = new Vector<String>();
vector.add(new String("hello"));
String str = vector.get(0);
Durante a compilação, esse código é reescrito em:
Vector vector = new Vector();
vector.add(new String("hello"));
String str = (String) vector.get(0);
O uso de genéricos Java não mudou muito sobre nossos recursos; apenas tornou as coisas um pouco mais bonitas. Por esse motivo, os genéricos Java às vezes são chamados de "açúcar sintático: '.
Isso é bem diferente do C ++. No C ++, os modelos são essencialmente um conjunto de macro glorificado, com o compilador criando uma nova cópia do código do modelo para cada tipo. Prova disso é que uma instância do MyClass não compartilha uma variável estática com o MyClass. No entanto, as instâncias de reboque do MyClass compartilharão uma variável 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
Em Java, variáveis estáticas são compartilhadas entre instâncias do MyClass, independentemente dos diferentes parâmetros do tipo.
Os modelos genéricos Java e C ++ têm várias outras diferenças. Esses incluem:
Modelos não passam de um sistema macro. Sintaxe de açúcar. Eles são totalmente expandidos antes da compilação real (ou, pelo menos, os compiladores se comportam como se fosse o caso).
Exemplo:
Digamos que queremos duas funções. Uma função pega duas seqüências (lista, matrizes, vetores, o que for necessário) de números e retorna seu produto interno. Outra função leva um comprimento, gera duas seqüências desse comprimento, as passa para a primeira função e retorna seu resultado. O problema é que podemos cometer um erro na segunda função, para que essas duas funções não sejam do mesmo tamanho. Precisamos do compilador para nos avisar neste caso. Não quando o programa está sendo executado, mas quando está compilando.
Em Java, você pode fazer algo assim:
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");
}
}
}
Em C #, você pode escrever quase a mesma coisa. Tente reescrevê-lo em C ++, e ele não será compilado, reclamando da expansão infinita de modelos.
Gostaria de citar askanydiferença aqui:
A principal diferença entre C ++ e Java reside na dependência da plataforma. Enquanto o C ++ é uma linguagem dependente da plataforma, Java é uma linguagem independente da plataforma.
A declaração acima é a razão pela qual o C ++ é capaz de fornecer tipos genéricos verdadeiros. Embora o Java tenha uma verificação rigorosa e, portanto, eles não permitem o uso de genéricos da maneira que o C ++ permite.