Quais são as diferenças entre os tipos "genéricos" em C ++ e Java?

Respostas:

144

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.

Alexandru Nedelcu
fonte
27
Ele está perfeitamente correto de que é apenas um açúcar sintático elaborado.
alphazero
31
Não é açúcar puramente sintático. O compilador usa essas informações para verificar os tipos. Mesmo que as informações não estejam disponíveis no tempo de execução, eu não chamaria algo que os compilados usam simplesmente "açúcar sintático". Se você chamá-lo assim, bem, então C é apenas açúcar sintático para montagem, e isso é só açúcar sintático para código de máquina :)
DTECH
42
Eu acho que o açúcar sintático é útil.
poitroae
5
Você perdeu um grande ponto de diferença, o que pode ser usado para instanciar um genérico. Em c ++, é possível usar o modelo <int N> e obter um resultado diferente para qualquer número usado para instancia-lo. É usado para meta progame em tempo de compilação. Como a resposta em: stackoverflow.com/questions/189172/c-templates-turing-complete
stonemetal
2
Você não precisa 'especificar um tipo', na forma de um extendsou de super. A resposta está incorreta #
Marquis of Lorne
124

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 #definemacro. Isso permite que você faça coisas como ter intparâ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 Mapfor 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 IEnumerablee 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;

  • Java permite delimitar argumentos.

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

cleto
fonte
1
Eles não são equivalentes em conceito. O melhor exemplo é o padrão curiosamente recorrente do modelo. O segundo melhor é o design orientado a políticas. O terceiro melhor é o fato de que o C ++ permite que números inteiros sejam passados ​​entre colchetes angulares (myArray <5>).
Max Lybbert
1
Não, eles não são equivalentes em conceito. Há alguma sobreposição no conceito, mas não muito. Ambos permitem que você crie a Lista <T>, mas é o mais longe possível. Modelos C ++ vão muito mais longe.
jalf
5
É importante observar que o problema de apagamento de tipo significa mais do que apenas compatibilidade com versões anteriores 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.
Yuval Adam
1
Você notará que eu disse "basicamente um pré-processador / macro glorificado". Foi uma analogia porque cada declaração de modelo criará mais código (em oposição a Java / C #).
Cletus
4
O código do modelo é muito diferente de copiar e colar. Se você pensar em termos de expansão de macro, mais cedo ou mais tarde você vai ser atingido por erros sutis como esta: womble.decadentplace.org.uk/c++/...
Nemanja Trifunovic
86

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.

 

Shog9
fonte
4
Essa é uma explicação concisa muito boa. Um ajuste que eu ficaria tentado a fazer é que os genéricos Java são uma ferramenta para gerar automaticamente previsões tipográficas que são seguras (com algumas condições). De certa forma, eles estão relacionados aos C ++ const. Um objeto em C ++ não será modificado por meio de um constponteiro, 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.
Laurence Gonsalves 01/03
16

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.

KeithB
fonte
1
+1 modelo de especialização é incrivelmente importante para metaprogramming em tempo de compilação - esta diferença em si torna os genéricos Java que muito menos potente
Faisal Vali
13

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 .

texto alternativo
(fonte: oreilly.com )

Julien Chastang
fonte
5

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

OscarRyz
fonte
3

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.

Ferruccio
fonte
3

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 sumcom Specialobjetos, o terceiro será chamado. Eu não acho que isso seja possível com Java.

KeithB
fonte
2
Pode ser porque o Java não tem ponteiros .. !! você pode explicar com um exemplo melhor?
Bhavuk Mathur
2

Vou resumir em uma única frase: modelos criam novos tipos, genéricos restringe tipos existentes.

Marquês de Lorne
fonte
2
Sua explicação é tão breve! E faz perfeitamente sentido para pessoas que entendem bem o tópico. Mas para pessoas que ainda não o entendem, isso não ajuda muito. (Que é o caso de alguém faz a pergunta no SO, entendeu?)
Jakub
1

@Keith:

Esse código está realmente errado e, além das pequenas falhas ( templateomitidas, 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); }
Konrad Rudolph
fonte
2
Por que isso é uma resposta e não um comentário?
Laurence Gonsalves 01/01
3
@ Laurence: pela primeira vez, porque foi publicado muito antes de os comentários serem implementados no Stack Overflow. Por outro, porque não é apenas um comentário - também é uma resposta para a pergunta: algo como o código acima não é possível em Java.
Konrad Rudolph
1

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 C ++ podem usar tipos primitivos, como int. Java não pode e deve usar Integer.
  • Em Java, você pode restringir os parâmetros de tipo do modelo para serem de um determinado tipo. Por exemplo, você pode usar genéricos para implementar um CardDeck e especificar que o parâmetro type deve se estender do CardGame.
  • No C ++, o parâmetro type pode ser instanciado, enquanto o Java não suporta isso.
  • Em Java, o parâmetro type (ou seja, o Foo em MyClass) não pode ser usado para métodos e variáveis ​​estáticos, pois eles seriam compartilhados entre MyClass e MyClass. No C ++, essas classes são diferentes, portanto o parâmetro type pode ser usado para métodos e variáveis ​​estáticas.
  • Em Java, todas as instâncias do MyClass, independentemente de seus parâmetros de tipo, são do mesmo tipo. Os parâmetros de tipo são apagados no tempo de execução. No C ++, instâncias com diferentes parâmetros de tipo são diferentes.
Jaycee
fonte
0

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.

MigMit
fonte
Ok, isso tem 3 anos, mas estou respondendo de qualquer maneira. Eu não entendo o seu ponto. Todo o motivo pelo qual Java gera um erro para essa linha comentada é porque você chamaria uma função que espera dois A com argumentos diferentes (A e Contras <A>) e isso é realmente básico e também acontece quando nenhum genérico está envolvido. O C ++ também faz isso. Além disso, esse código me deu câncer porque é realmente horrível. No entanto, você ainda faria isso no C ++, pois é necessário fazer algumas modificações, porque o C ++ não é Java, mas isso não é uma desvantagem dos modelos do C ++.
Clock16 de
@clocktown não, você não pode fazer isso em C ++. Nenhuma quantidade de modificações permitiria isso. E isso é uma desvantagem dos modelos C ++.
MigMit
O que seu código deveria fazer - avisar sobre diferentes comprimentos - não funciona. No seu exemplo comentado, ele só produz erros devido a argumentos não correspondentes. Isso funciona em C ++ também. Você pode digitar código que seja semanticamente equivalente e muito melhor que essa bagunça em C ++ e em Java.
Clock6
Faz. Os argumentos não correspondem exatamente porque os comprimentos são diferentes. Você não pode fazer isso em C ++.
MigMit
0

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.

Piyush-Ask Any Difference
fonte