Pergunta

Java possui genéricos e C++ fornece um modelo de programação muito forte com templateS.Então, qual é a diferença entre genéricos C++ e Java?

Foi útil?

Solução

Há uma grande diferença entre eles.Em C++ você não precisa especificar uma classe ou 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 possua 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 ); }

Em 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 é invocado).Portanto a compilação é mais lenta.Em Java a compilação não tem grande penalidade, mas Java usa uma técnica chamada "apagamento" onde o tipo genérico é apagado em tempo de execução, então em 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.

EDITAR: a opinião acima sobre a utilidade foi escrita por uma pessoa mais jovem.Os genéricos do Java ajudam na segurança de tipo, é claro.

Outras dicas

Genéricos Java são massivamente diferente dos modelos C++.

Basicamente, em modelos C++, há basicamente um conjunto de pré-processador/macro glorificado (Observação: 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 padrão de objetos.Aqui está um bastante decente introdução aos modelos C++ versus genéricos Java.

Para elaborar este ponto:quando você usa um modelo C++, você basicamente cria outra cópia do código, como se usasse um #define macro.Isso permite que você faça coisas como ter int parâmetros em definições de modelo que determinam tamanhos de matrizes e outros.

Java não funciona assim.Em Java, todos os objetos se estendem de java.lang.Object então, 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 tipo base para que você pudesse colocar qualquer coisa neles.Java 5 é implementado 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 que os Java Generics são:invólucros para lançar objetos.Isso ocorre porque os genéricos Java não são refinados.Eles usam apagamento de tipo.Essa decisão foi tomada porque o Java Generics surgiu tão tarde que eles não queriam quebrar a compatibilidade com versões anteriores (uma Map<String, String> é utilizável sempre que um Map é 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 e IEnumerable e IEnumerable<T> não têm relação entre si).

E uma classe que usa genéricos compilados com um compilador Java 5+ pode ser usada no JDK 1.4 (assumindo que não use nenhum outro recurso ou classe que exija Java 5+).

É por isso que os genéricos Java são chamados açúcar sintático.

Mas esta decisão sobre como fazer genéricos tem efeitos tão profundos que o (excelente) Perguntas frequentes sobre genéricos Java surgiu para responder às muitas perguntas que as pessoas têm sobre Java Generics.

Os modelos C++ possuem vários recursos que o Java Generics não possui:

  • Uso de argumentos de 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 há motivos de compatibilidade com versões anteriores para isso;

  • Java permite delimitação de argumentos.

Por exemplo:

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

Realmente precisa ser enfatizado que as invocações de modelos com argumentos diferentes são, na verdade, tipos diferentes.Eles nem compartilham membros estáticos.Em Java este não é o caso.

Além das diferenças com os genéricos, para completar, aqui está um comparação básica de C++ e Java (e outro).

E também posso sugerir Pensando em Java.Como um programador C++, muitos conceitos como objetos já serão uma segunda natureza, mas existem diferenças sutis, então pode valer a pena ter um texto introdutório, mesmo se você folhear as partes.

Muito do que você aprenderá ao aprender Java são todas as bibliotecas (tanto padrão - o que vem no JDK - quanto não 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++.

C++ possui modelos.Java tem 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 (espere...) que ele pode usar para gerar código seguro de tipo, preenchendo os parâmetros do modelo.

Os genéricos, como eu os entendo, funcionam ao contrário:os parâmetros de tipo são usados ​​pelo compilador para verificar se o código que os utiliza é seguro para o tipo, mas o código resultante é gerado sem nenhum tipo.

Pense nos modelos C++ como um muito bom sistema macro e genéricos Java como uma ferramenta para gerar automaticamente typecasts.

 

Outro recurso que os modelos C++ possuem e que os genéricos Java não possuem é 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 interno, embora ainda tenha 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 desreferenciado quando recebe um ponteiro.

Há uma ótima explicação sobre esse assunto em Genéricos e coleções JavaPor Maurice Naftalin, Philip Wadler.Eu recomendo fortemente este livro.Citar:

Os genéricos em Java se assemelham a modelos em C ++....A sintaxe é deliberadamente semelhante e a semântica é deliberadamente diferente....Semanticamente, os genéricos do Java são definidos pelo apagamento, onde os modelos de C ++ são definidos pela expansão.

Por favor leia a explicação completa aqui.

alt text
(fonte: oreilly.com)

Basicamente, os modelos AFAIK e 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 falar esse modelo C++ é equivalente ao genérico Java conceito (embora seja mais correto dizer que os genéricos Java são equivalentes a C++ em conceito)

Se você estiver familiarizado com o mecanismo de modelo do C++, poderá 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 “metaprogramação de modelos”.

de: Genéricos Java

Os genéricos Java (e C#) parecem ser um mecanismo simples de substituição de tipo em tempo de execução.
Os modelos C++ são uma construção em tempo de compilação que oferece uma maneira de 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 sum com ponteiros, o segundo método será chamado, se você chamar sum com objetos que não sejam ponteiros, o primeiro método será chamado, e se você chamar sum com Special objetos, o terceiro será chamado.Não acho que isso seja possível com Java.

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

@Keith:

Esse código está realmente errado e, além das falhas menores (template omitido, a sintaxe de especialização parece diferente), especialização parcial não trabalhe em modelos de função, apenas em modelos de classe.No entanto, o código funcionaria sem especialização parcial de modelo, em vez de usar a sobrecarga antiga:

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 Quebrando a entrevista de codificação Soluções para o Capítulo 13, que considero muito boas.

A implementação de genéricos Java está enraizada em uma ideia de "apagamento de tipo: 'Esta técnica elimina os tipos parametrizados quando o código-fonte é traduzido para o 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, este 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 nossas capacidades;apenas tornou as coisas um pouco mais bonitas.Por esta razão, os genéricos Java são às vezes chamados de"açúcar sintático:'.

Isso é bem diferente de C++.Em C++, os modelos são essencialmente um conjunto de macros glorificado, com o compilador criando uma nova cópia do código do modelo para cada tipo.Prova disso está no fato de que uma instância de MyClass não compartilhará uma variável estática comMyClass.Duas instâncias de MyClass, entretanto, 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 de MyClass, independentemente dos diferentes parâmetros de tipo.

Os genéricos Java e os modelos C++ têm uma série de outras diferenças.Esses incluem:

  • Os modelos C++ podem usar tipos primitivos, como int.Java não pode e deve usar o número inteiro.
  • Em Java, você pode restringir os parâmetros de tipo do modelo a ter um determinado tipo.Por exemplo, você pode usar genéricos para implementar um CardDeck e especificar que o parâmetro de tipo deve se estender do CardGame.
  • No C ++, o parâmetro de tipo pode ser instanciado, enquanto o Java não suporta isso.
  • Em Java, o parâmetro de tipo (isto é, o Foo na MyClass) não pode ser usado para métodos e variáveis ​​estáticos, pois estes seriam compartilhados entre MyClass e MyClass.Em C++, essas classes são diferentes, portanto o parâmetro de tipo pode ser usado para métodos e variáveis ​​estáticas.
  • Em Java, todas as instâncias de MyClass, independentemente de seus parâmetros de tipo, são do mesmo tipo.Os parâmetros de tipo são apagados em tempo de execução.Em C++, instâncias com parâmetros de tipo diferentes são tipos diferentes.

Os modelos nada mais são do que um sistema macro.Açúcar de sintaxe.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 sequências (lista, arrays, vetores, o que quer que seja) de números e retorna seu produto interno.Outra função pega um comprimento, gera duas sequências desse comprimento, passa-as para a primeira função e retorna seu resultado.O problema é que podemos cometer um erro na segunda função, de modo que essas duas funções não tenham realmente o mesmo comprimento.Precisamos que o compilador nos avise neste caso.Não quando o programa está em execução, 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 dos templates.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top