Jakie są różnice między typami „ogólnymi” w językach C ++ i Java?

Odpowiedzi:

144

Jest między nimi duża różnica. W C ++ nie musisz określać klasy ani interfejsu dla typu ogólnego. Dlatego możesz tworzyć prawdziwie ogólne funkcje i klasy, z zastrzeżeniem luźniejszego pisania.

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

Powyższa metoda dodaje dwa obiekty tego samego typu i może być używana dla dowolnego typu T, który ma dostępny operator „+”.

W Javie musisz określić typ, jeśli chcesz wywołać metody na przekazanych obiektach, na przykład:

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

W C ++ ogólne funkcje / klasy można definiować tylko w nagłówkach, ponieważ kompilator generuje różne funkcje dla różnych typów (z którymi jest wywoływany). Więc kompilacja jest wolniejsza. W Javie kompilacja nie ma większej kary, ale Java używa techniki zwanej „erasure”, w której typ ogólny jest kasowany w czasie wykonywania, więc w czasie wykonywania Java faktycznie wywołuje ...

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

Tak więc programowanie ogólne w Javie nie jest zbyt użyteczne, to tylko niewielka część składniowa pomocna w nowej konstrukcji foreach.

EDYCJA: powyższa opinia na temat użyteczności została napisana przez młodszą osobę. Typy generyczne języka Java pomagają oczywiście w bezpieczeństwie typów.

Alexandru Nedelcu
źródło
27
Ma całkowitą rację, że to tylko wyszukany cukier syntaktyczny.
alphazero
31
To nie jest wyłącznie cukier syntaktyczny. Kompilator używa tych informacji do sprawdzania typów. Nawet jeśli informacje nie są dostępne w czasie wykonywania, nie nazwałbym czegoś, co kompiluje po prostu „cukier syntaktyczny”. Gdybyś tak to nazwał, cóż, C to tylko cukier syntaktyczny do asemblacji, a to tylko cukier syntaktyczny dla kodu maszynowego :)
dtech
42
Myślę, że cukier syntaktyczny jest przydatny.
poitroae
5
Przegapiłeś główną różnicę, której możesz użyć do utworzenia wystąpienia generycznego. W języku c ++ można użyć template <int N> i uzyskać inny wynik dla dowolnej liczby użytej do jego utworzenia. Służy do metaprogramowania czasu kompilacji. Podobnie jak odpowiedź w: stackoverflow.com/questions/189172/c-templates-turing-complete
stonemetal
2
Zdajesz nie muszą „określić typ”, w postaci jednej extendslub super. Odpowiedź jest nieprawidłowa,
Markiz Lorne
124

Java Generics znacznie różni się od szablonów C ++.

Zasadniczo w C ++ szablony są w zasadzie gloryfikowanym zestawem preprocesorów / makr ( Uwaga: ponieważ niektórzy ludzie nie są w stanie zrozumieć analogii, nie mówię, że przetwarzanie szablonów jest makrem). W Javie są one w zasadzie cukrem syntaktycznym, aby zminimalizować standardowe rzutowanie obiektów. Oto całkiem przyzwoite wprowadzenie do szablonów C ++ w porównaniu z generycznymi językami Java .

Aby rozwinąć ten punkt: kiedy używasz szablonu C ++, w zasadzie tworzysz kolejną kopię kodu, tak jakbyś używał #definemakra. Pozwala to na wykonywanie takich czynności, jak umieszczanie intparametrów w definicjach szablonów, które określają rozmiary tablic i tym podobne.

Java tak nie działa. W Javie wszystkie obiekty pochodzą z java.lang.Object, więc w wersji pre-Generics można napisać taki kod:

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

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

  ...
}

ponieważ wszystkie typy kolekcji Java używały Object jako typu podstawowego, więc można było w nich umieścić cokolwiek. Java 5 toczy się i dodaje typy generyczne, dzięki czemu możesz wykonywać następujące czynności:

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

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

  ...
}

I to wszystko, co generics Java to: opakowania do rzutowania obiektów. To dlatego, że Java Generics nie jest udoskonalana. Używają wymazywania typów. Ta decyzja została podjęta, ponieważ Java Generics pojawiła się tak późno, że nie chcieli zrywać wstecznej kompatybilności (a Map<String, String>jest używany zawsze, gdy Mapjest wymagany). Porównaj to z .Net / C #, gdzie wymazywanie typów nie jest używane, co prowadzi do różnego rodzaju różnic (np. Możesz używać typów pierwotnych IEnumerablei IEnumerable<T>nie mieć ze sobą żadnego związku).

A klasa korzystająca z typów generycznych skompilowana za pomocą kompilatora Java 5+ jest użyteczna w JDK 1.4 (zakładając, że nie używa żadnych innych funkcji ani klas, które wymagają Java 5+).

Dlatego generiki Java są nazywane cukrem syntaktycznym .

Ale ta decyzja, jak zrobić generyczne, ma tak głębokie skutki, że pojawiło się (znakomite) FAQ dotyczące generycznych języka Java, aby odpowiedzieć na wiele, wiele pytań, jakie ludzie mają na temat generycznych języków Java.

Szablony C ++ mają wiele funkcji, których nie mają Java Generics:

  • Używanie argumentów typu pierwotnego.

    Na przykład:

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

    Java nie zezwala na używanie argumentów typu pierwotnego w typach ogólnych.

  • Użycie domyślnych argumentów typu , jest to jedna z funkcji, której brakuje mi w Javie, ale są ku temu powody kompatybilności wstecznej;

  • Java umożliwia ograniczanie argumentów.

Na przykład:

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

Naprawdę należy podkreślić, że wywołania szablonów z różnymi argumentami są naprawdę różnymi typami. Nie mają nawet wspólnych statycznych członków. W Javie tak nie jest.

Oprócz różnic z rodzajami generycznymi, dla kompletności, tutaj jest podstawowe porównanie C ++ i Java (i jeszcze jedno ).

Mogę też zasugerować Thinking in Java . Jako programista C ++ wiele pojęć, takich jak obiekty, będzie już drugą naturą, ale istnieją subtelne różnice, więc warto mieć tekst wprowadzający, nawet jeśli przeglądasz części.

Wiele z tego, czego nauczysz się podczas nauki języka Java, to wszystkie biblioteki (zarówno standardowe - zawarte w JDK - jak i niestandardowe, w tym powszechnie używane rzeczy, takie jak Spring). Składnia Java jest bardziej rozwlekła niż składnia C ++ i nie ma wielu funkcji C ++ (np. Przeciążanie operatorów, wielokrotne dziedziczenie, mechanizm destruktora itp.), Ale to też nie czyni z niej ściśle podzbioru C ++.

cletus
źródło
1
Nie są równoważne w koncepcji. Najlepszym przykładem jest ciekawie powtarzający się wzór szablonu. Drugi w kolejności projekt zorientowany na politykę. Trzecią z najlepszych jest fakt, że C ++ umożliwia przekazywanie liczb całkowitych w nawiasach ostrych (myArray <5>).
Max Lybbert
1
Nie, nie są one równoważne w koncepcji. Koncepcja ta trochę się pokrywa, ale niewiele. Oba pozwalają na tworzenie List <T>, ale to mniej więcej tak daleko. Szablony C ++ idą dużo dalej.
jalf
5
Należy zauważyć, że problem usuwania typów oznacza coś więcej niż tylko wsteczną kompatybilność dla Map map = new HashMap<String, String>. Oznacza to, że możesz wdrożyć nowy kod na starej maszynie JVM i będzie on działał ze względu na podobieństwa w kodzie bajtowym.
Yuval Adam
1
Zauważysz, że powiedziałem „w zasadzie gloryfikowany preprocesor / makro”. To była analogia, ponieważ każda deklaracja szablonu stworzy więcej kodu (w przeciwieństwie do Java / C #).
cletus
4
Kod szablonu bardzo różni się od kopiowania i wklejania. Jeśli myślisz w kategoriach ekspansji makr, prędzej czy później natrafisz na
Nemanja Trifunovic
86

C ++ ma szablony. Java ma typy generyczne, które wyglądają trochę jak szablony C ++, ale są bardzo, bardzo różne.

Szablony działają, jak sama nazwa wskazuje, dostarczając kompilatorowi szablon (czekaj na to ...), którego może użyć do wygenerowania kodu bezpiecznego dla typu, wypełniając parametry szablonu.

Generics, jak je rozumiem, działają na odwrót: kompilator używa parametrów typu do sprawdzenia, czy kod, który ich używa, jest bezpieczny dla typów, ale wynikowy kod jest generowany bez typów.

Pomyśl o szablonach C ++ jako o naprawdę dobrym systemie makr, a generiki Java jako o narzędziu do automatycznego generowania typecastów.

 

Shog9
źródło
4
To całkiem dobre, zwięzłe wyjaśnienie. Jedną z poprawek, które chciałbym wprowadzić, jest to, że generyczne programy Java to narzędzie do automatycznego generowania typów typów, które z pewnością są bezpieczne (pod pewnymi warunkami). W pewnym sensie są one powiązane z C ++ const. Obiekt w C ++ nie zostanie zmodyfikowany przez constwskaźnik, chyba że const-ness zostanie odrzucone. Podobnie, niejawne rzutowania utworzone przez typy ogólne w Javie są gwarantowane jako „bezpieczne”, chyba że parametry typu są ręcznie odrzucane gdzieś w kodzie.
Laurence Gonsalves
16

Inną cechą szablonów C ++, której nie mają generyczne programy Java, jest specjalizacja. To pozwala mieć inną implementację dla określonych typów. Możesz więc na przykład mieć wysoce zoptymalizowaną wersję dla int , jednocześnie mając wersję ogólną dla pozostałych typów. Lub możesz mieć różne wersje dla typów wskaźnikowych i innych niż wskaźnikowe. Jest to przydatne, jeśli chcesz operować na wyłowionym obiekcie, gdy podajesz mu wskaźnik.

KeithB
źródło
1
Specjalizacja szablonów +1 jest niezwykle ważna dla metaprogramowania w czasie kompilacji - ta różnica sama w sobie sprawia, że ​​generyczne programy java są znacznie słabsze
Faisal Vali
13

Świetne wyjaśnienie tego tematu można znaleźć w książkach Java Generics and Collections autorstwa Maurice'a Naftalina, Philipa Wadlera. Bardzo polecam tę książkę. Cytować:

Generics w Javie przypominają szablony w C ++. ... Składnia jest celowo podobna, a semantyka celowo inna. ... Semantycznie rzecz biorąc, typy generyczne Javy są definiowane przez wymazywanie, gdzie szablony C ++ są definiowane przez rozwinięcie.

Przeczytaj pełne wyjaśnienie tutaj .

tekst alternatywny
(źródło: oreilly.com )

Julien Chastang
źródło
5

Zasadniczo szablony AFAIK, C ++ tworzą kopię kodu dla każdego typu, podczas gdy generics Java używają dokładnie tego samego kodu.

Tak ty powiedzieć, że szablon C ++ jest odpowiednikiem ogólnej koncepcji Javy (chociaż lepiej byłoby powiedzieć, że generyczne typy Java są równoważne z C ++ w koncepcji)

Jeśli znasz mechanizm szablonów C ++, możesz pomyśleć, że typy generyczne są podobne, ale podobieństwo jest powierzchowne. Typy generyczne nie generują nowej klasy dla każdej specjalizacji ani nie pozwalają na „metaprogramowanie szablonów”.

z: Java Generics

OscarRyz
źródło
3

Wydaje się, że typy generyczne Java (i C #) są prostym mechanizmem zastępowania typów w czasie wykonywania.
Szablony C ++ to konstrukcja tworzona w czasie kompilacji, która umożliwia modyfikowanie języka w celu dostosowania go do własnych potrzeb. W rzeczywistości są one czysto funkcjonalnym językiem, który kompilator wykonuje podczas kompilacji.

Ferruccio
źródło
3

Kolejną zaletą szablonów C ++ jest specjalizacja.

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

Teraz, jeśli wywołasz sum za pomocą wskaźników, zostanie wywołana druga metoda, jeśli wywołasz sum z obiektami niebędącymi wskaźnikami, zostanie wywołana pierwsza metoda, a jeśli wywołasz sumz Specialobiektami, zostanie wywołana trzecia. Nie sądzę, żeby było to możliwe w Javie.

KeithB
źródło
2
Może dlatego, że Java nie ma wskaźników .. !! czy możesz wyjaśnić na lepszym przykładzie?
Bhavuk Mathur
2

Podsumuję to jednym zdaniem: szablony tworzą nowe typy, rodzaje generyczne ograniczają istniejące typy.

Markiz Lorne
źródło
2
Twoje wyjaśnienie jest takie krótkie! I ma sens dla osób, które dobrze rozumieją temat. Ale dla ludzi, którzy jeszcze tego nie rozumieją, nie pomaga to zbytnio. (Co jest w przypadku, gdy ktoś zadaje pytanie na SO, rozumiem?)
Jakub
1

@Keith:

Ten kod jest właściwie błędny i poza mniejszymi usterkami ( templatepominięty, składnia specjalizacji wygląda inaczej), częściowa specjalizacja nie działa na szablonach funkcji, tylko na szablonach klas. Kod działałby jednak bez częściowej specjalizacji szablonów, zamiast tego używałby zwykłego starego przeciążenia:

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
źródło
2
Dlaczego jest to odpowiedź, a nie komentarz?
Laurence Gonsalves
3
@Laurence: chociaż raz, ponieważ został opublikowany na długo przed zaimplementowaniem komentarzy na Stack Overflow. Po drugie, bo to nie tylko komentarz - to także odpowiedź na pytanie: coś takiego jak powyższy kod nie jest możliwe w Javie.
Konrad Rudolph
1

Odpowiedź poniżej pochodzi z książki Cracking The Coding Interview Solutions do rozdziału 13, która moim zdaniem jest bardzo dobra.

Implementacja typów ogólnych języka Java jest zakorzeniona w idei „wymazywania typów:” Ta technika eliminuje sparametryzowane typy, gdy kod źródłowy jest tłumaczony na kod bajtowy wirtualnej maszyny języka Java (JVM). Załóżmy na przykład, że masz poniższy kod Java:

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

Podczas kompilacji ten kod jest przepisywany do:

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

Użycie typów generycznych Java nie zmieniło zbytnio naszych możliwości; po prostu sprawiło, że było trochę ładniej. Z tego powodu typy generyczne języka Java są czasami nazywane „cukrem syntaktycznym”.

To jest zupełnie inne niż C ++. W C ++ szablony są zasadniczo gloryfikowanym zestawem makr, w którym kompilator tworzy nową kopię kodu szablonu dla każdego typu. Dowodem na to jest fakt, że wystąpienie MyClass nie będzie współużytkować zmiennej statycznej z MyClass. Jednak wystąpienia MyClass będą miały wspólną zmienną statyczną.

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

W Javie zmienne statyczne są współużytkowane przez instancje MyClass, niezależnie od różnych parametrów typu.

Generics Java i C ++ szablony mają wiele innych różnic. Obejmują one:

  • Szablony C ++ mogą używać typów pierwotnych, takich jak int. Java nie może i musi zamiast tego używać liczby całkowitej.
  • W Javie można ograniczyć parametry typu szablonu, aby były określonego typu. Na przykład możesz użyć typów ogólnych, aby zaimplementować CardDeck i określić, że parametr typu musi być rozszerzony z CardGame.
  • W C ++ można utworzyć wystąpienie parametru typu, podczas gdy Java tego nie obsługuje.
  • W Javie parametr type (tj. Foo w MyClass) nie może być używany dla statycznych metod i zmiennych, ponieważ byłyby one współdzielone między MyClass i MyClass. W C ++ te klasy są różne, więc parametr typu może być używany dla statycznych metod i zmiennych.
  • W Javie wszystkie wystąpienia MyClass, niezależnie od ich parametrów typu, są tego samego typu. Parametry typu są usuwane w czasie wykonywania. W C ++ instancje z różnymi parametrami typu są różnymi typami.
Jaycee
źródło
0

Szablony to nic innego jak system makr. Cukier składniowy. Są w pełni rozwinięte przed właściwą kompilacją (lub przynajmniej kompilatory zachowują się tak, jakby tak było).

Przykład:

Powiedzmy, że chcemy dwóch funkcji. Jedna funkcja przyjmuje dwie sekwencje (listę, tablice, wektory, cokolwiek pójdzie) liczb i zwraca ich iloczyn wewnętrzny. Inna funkcja przyjmuje długość, generuje dwie sekwencje o tej długości, przekazuje je do pierwszej funkcji i zwraca wynik. Problem polega na tym, że możemy popełnić błąd w drugiej funkcji, tak że te dwie funkcje nie są tak naprawdę tej samej długości. W tym przypadku potrzebujemy kompilatora, który ostrzega nas. Nie kiedy program jest uruchomiony, ale kiedy się kompiluje.

W Javie możesz zrobić coś takiego:

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

W C # można napisać prawie to samo. Spróbuj przepisać go w C ++ i nie będzie się kompilował, narzekając na nieskończoną rozbudowę szablonów.

MigMit
źródło
OK, to ma 3 lata, ale i tak odpowiadam. Nie widzę twojego punktu widzenia. Cały powód, dla którego Java generuje błąd dla tej skomentowanej linii, polega na tym, że wywołujesz funkcję, która oczekuje dwóch A z różnymi argumentami (A i Wady <A>) i jest to naprawdę podstawowe i zdarza się również, gdy nie są zaangażowane żadne typy ogólne. C ++ też to robi. Poza tym ten kod dał mi raka, ponieważ jest naprawdę okropny. Jednak nadal byś to robił w C ++, musisz więc oczywiście modyfikować, ponieważ C ++ nie jest Javą, ale nie jest to wadą szablonów C ++.
Clocktown
@clocktown nie, NIE MOŻESZ tego zrobić w C ++. Żadna ilość modyfikacji na to nie pozwoliła. I to JEST wada szablonów C ++.
MigMit
To, co miał robić Twój Kod - ostrzegaj o innej długości - nie działa. W Twoim zakomentowanym przykładzie generuje błędy tylko z powodu niezgodnych argumentów. Działa to również w C ++. Możesz wpisać kod, który jest semantycznie równoważny i znacznie lepszy niż ten bałagan w C ++ i Javie.
Clocktown
To robi. Argumenty nie pasują dokładnie, ponieważ długości są różne. Nie możesz tego zrobić w C ++.
MigMit
0

Chciałbym zacytować tutaj pytanie o różnicę :

Główna różnica między C ++ a Javą polega na ich zależności od platformy. Chociaż C ++ jest językiem zależnym od platformy, Java jest językiem niezależnym od platformy.

Powyższe stwierdzenie jest powodem, dla którego C ++ jest w stanie zapewnić prawdziwe typy generyczne. Chociaż Java ma ścisłe sprawdzanie i dlatego nie pozwala na używanie typów ogólnych w sposób, w jaki pozwala na to C ++.

Piyush-Ask Any Difference
źródło