Jakie są różnice między generykami w C# i Javie... a szablonami w C++? [zamknięte]

Używam głównie Javy i generyki są stosunkowo nowe. Ciągle czytam, że Java podjęła złą decyzję lub że. NET ma lepsze implementacje itp. itd.

Jakie są główne różnice między C++, C#, Java w generykach? Plusy / minusy każdego?

Author: Shog9, 2008-08-28

13 answers

[[30]}dodam swój głos do szumu i postaram się wszystko wyjaśnić:

C # Generics pozwala zadeklarować coś takiego.

List<Person> foo = new List<Person>();

I wtedy kompilator uniemożliwi umieszczanie rzeczy, które nie są Person na liście.
Za kulisami kompilator C# umieszcza List<Person> w pliku. Net dll, ale w czasie wykonywania kompilator JIT buduje nowy zestaw kodu, tak jakbyś napisał specjalną klasę list zawierającą ludzi - coś w rodzaju ListOfPerson.

Zaletą tego jest to, że robi to naprawdę szybko. Nie ma żadnego castingu ani żadnych innych rzeczy, a ponieważ dll zawiera informację, że jest to lista Person, inny kod, który spojrzy na nią później za pomocą reflection, może powiedzieć, że zawiera obiekty Person (więc dostajesz intellisense itd.).

Minusem tego jest to, że stary kod C# 1.0 i 1.1 (przed dodaniem generics) nie rozumie tych nowych List<something>, więc trzeba ręcznie konwertować rzeczy z powrotem do zwykły stary List, aby współdziałać z nimi. Nie jest to aż tak duży problem, ponieważ kod binarny C # 2.0 nie jest kompatybilny wstecz. Tylko wtedy, gdy zaktualizujesz stary kod C# 1.0/1.1 do C# 2.0.]}

Java Generics pozwala zadeklarować coś takiego.

ArrayList<Person> foo = new ArrayList<Person>();
Na powierzchni wygląda tak samo i tak jakby jest. Kompilator uniemożliwi również umieszczanie na liście rzeczy, które nie są Person.

Różnica polega na tym, co dzieje się za kulisami. W przeciwieństwie do C#, Java nie buduje specjalnego ListOfPerson - używa tylko zwykłego starego ArrayList, który zawsze był w Javie. Kiedy Pozbierasz rzeczy z tablicy, zwykły Person p = (Person)foo.get(1); casting-taniec nadal musi być wykonany. Kompilator oszczędza naciśnięcia klawiszy, ale prędkość uderzania / rzucania jest nadal taka, jak zawsze.
Kiedy ludzie wspominają o "wymazywaniu typu", to właśnie o tym mówią. Kompilator wstawia za Ciebie odlewy, a następnie "kasuje" fakt że ma to być lista Person nie tylko Object

Zaletą tego podejścia jest to, że stary kod, który nie rozumie generyków, nie musi się tym przejmować. Wciąż ma do czynienia z tym samym starym ArrayList jak zawsze. Jest to ważniejsze w świecie Javy, ponieważ chcieli wspierać kompilowanie kodu za pomocą Javy 5 z generics i uruchamianie go na starych 1.4 lub poprzednich JVM-ach, które microsoft celowo postanowił nie zawracać sobie głowy.

Minusem jest prędkość hit I wspomnianą wcześniej, a także dlatego, że nie ma ListOfPerson pseudo-klasy czy czegoś takiego wchodzącego dopliki klas, kod, który spojrzy na niego później (z odbiciem, lub jeśli wyciągniesz go z innej kolekcji, gdzie został przekonwertowany do Object itd.) nie może w żaden sposób powiedzieć, że ma to być lista zawierająca tylko Person, a nie jakakolwiek inna lista tablic.

Szablony C++ pozwalają zadeklarować coś takiego

std::list<Person>* foo = new std::list<Person>();

Wygląda jak C # i Java generics, a zrobi to, co uważasz, że powinno, ale za kulisami dzieją się różne rzeczy.

Ma najwięcej wspólnego z C# generics, ponieważ buduje specjalne pseudo-classes, a nie tylko wyrzuca informacje o typie, jak robi to java, ale to zupełnie inny kocioł ryb.

Zarówno C#, jak i Java wytwarzają dane wyjściowe przeznaczone dla maszyn wirtualnych. Jeśli napiszesz jakiś kod, który ma w sobie klasę Person, w obu przypadkach niektóre informacje o klasie Person trafią do the .dll lub .plik klasy, A JVM / CLR zrobi z tym coś

C++ tworzy surowy kod binarny x86. Wszystko jest , a nie obiektem i nie ma podstawowej maszyny wirtualnej, która musi wiedzieć o klasie Person. Nie ma boksu ani unboxingu, a funkcje nie muszą należeć do klas, ani do czegokolwiek.

Z tego powodu kompilator C++ nie nakłada żadnych ograniczeń na to, co można zrobić z szablonami - w zasadzie każdy kod, który można napisać ręcznie, można uzyskać szablony do napisania dla Ciebie.
Najbardziej oczywistym przykładem jest dodawanie rzeczy:

W C# i Javie, system generics musi wiedzieć, jakie metody są dostępne dla danej klasy i musi przekazać to do maszyny wirtualnej. Jedynym sposobem, aby to powiedzieć, jest albo twarde kodowanie rzeczywistej klasy w, lub za pomocą interfejsów. Na przykład:

string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }

Ten kod nie będzie kompilowany w C# lub Javie, ponieważ nie wie, że typ T faktycznie dostarcza metodę nazwaną Name (). Masz aby to powiedzieć - w C# tak:

interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }

A następnie musisz upewnić się, że rzeczy, które przekazujesz do addNames implementują interfejs IHasName i tak dalej. Składnia Javy jest inna (<T extends IHasName>), ale cierpi na te same problemy.

'klasycznym' przypadkiem dla tego problemu jest próba napisania funkcji, która to robi

string addNames<T>( T first, T second ) { return first + second; }

Nie możesz napisać tego kodu, ponieważ nie ma w nim możliwości zadeklarowania interfejsu za pomocą metody +. Zawiedliście.

C++ nie cierpi na żaden z tych problemów. Kompilator nie dba o przekazywanie typów do maszyn wirtualnych - jeśli oba obiekty mają funkcję .Name (), skompiluje się. Jeśli nie, to nie będzie.

No i masz: -)

 365
Author: Orion Edwards,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-26 03:22:11

C++ rzadko używa terminologii "generics". Zamiast tego słowo" szablony " jest używane i jest bardziej dokładne. Szablony opisują jedną technikę , aby uzyskać ogólny projekt.

Szablony C++ bardzo różnią się od tego, co implementują zarówno C#, jak i Java z dwóch głównych powodów. Pierwszym powodem jest to, że szablony C++ nie pozwalają tylko na argumenty typu compile-time, ale także argumenty const-value: szablony mogą być podane jako liczby całkowite lub nawet podpisy funkcji. Oznacza to, że może zrobić kilka ciekawych rzeczy w czasie kompilacji, np. obliczenia:

template <unsigned int N>
struct product {
    static unsigned int const VALUE = N * product<N - 1>::VALUE;
};

template <>
struct product<1> {
    static unsigned int const VALUE = 1;
};

// Usage:
unsigned int const p5 = product<5>::VALUE;

Ten kod wykorzystuje również inną wyróżniającą się cechę szablonów C++, a mianowicie specjalizację szablonów. Kod definiuje jeden szablon klasy product, który ma jeden argument wartości. Definiuje również specjalizację dla tego szablonu, który jest używany, gdy argument jest ewaluowany do 1. To pozwala mi zdefiniować rekurencję nad definicjami szablonów. Sądzę, że po raz pierwszy odkrył to Andriej Alexandrescu .

Specjalizacja szablonów jest ważna dla C++ , ponieważ pozwala na różnice strukturalne w strukturach danych. Szablony jako całość to sposób na ujednolicenie interfejsu między typami. Jednakże, chociaż jest to pożądane, wszystkie typy nie mogą być traktowane jednakowo wewnątrz implementacji. C++ templates bierze to pod uwagę. Jest to taka sama różnica, jaką OOP robi między interfejsem a implementacją z nadrzędnością metod wirtualnych.

C++ szablony są niezbędne dla jego algorytmicznego paradygmatu programowania. Na przykład prawie wszystkie algorytmy dla kontenerów są zdefiniowane jako funkcje, które akceptują typ kontenera jako typ szablonu i traktują go jednolicie. Właściwie to nie jest to do końca prawda: C++ nie działa na kontenerach, ale raczej na zakresach , które są zdefiniowane przez dwa Iteratory, wskazujące początek i koniec kontenera. Tak więc cała zawartość jest ograniczona przez Iteratory: begin

Używanie iteratorów zamiast kontenerów jest użyteczne, ponieważ pozwala operować na częściach kontenera zamiast na całości.

Kolejną cechą wyróżniającą C++ jest możliwość częściowej specjalizacji dla szablonów klas. Jest to w pewnym sensie związane z dopasowywaniem wzorców na argumentach w Haskell i innych językach funkcyjnych. Na przykład, rozważmy klasę, która przechowuje elementy:

template <typename T>
class Store { … }; // (1)

To działa dla każdego typu elementu. Ale powiedzmy, że my może przechowywać wskaźniki bardziej efektywnie niż inne typy, stosując jakąś specjalną sztuczkę. Możemy to zrobić poprzez częściowo specjalizując się dla wszystkich typów wskaźników:

template <typename T>
class Store<T*> { … }; // (2)

Teraz, gdy przykładamy szablon kontenera dla jednego typu, używana jest odpowiednia definicja:

Store<int> x; // Uses (1)
Store<int*> y; // Uses (2)
Store<string**> z; // Uses (2), with T = string*.
 61
Author: Konrad Rudolph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-19 01:29:33

Sam Anders Hejlsberg opisał tutaj różnice " Generics in C#, Java, and C++".

 35
Author: jfs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-28 05:14:53

Istnieje już wiele dobrych odpowiedzi na co różnice są, więc pozwól mi dać nieco inną perspektywę i dodać Dlaczego.

Jak już wyjaśniono, główną różnicą jest Type erasure, tzn. fakt, że kompilator Javy kasuje typy generyczne i nie kończą się one w wygenerowanym bajtowym kodzie. Jednak pytanie brzmi: Dlaczego ktoś miałby to zrobić? To nie ma sensu! A może tak?

A jaka jest alternatywa? Jeśli nie zaimplementować generyki w języku, gdzie Czy je implementujesz? A odpowiedź brzmi: w maszynie wirtualnej. Co łamie wsteczną kompatybilność.

Type erasure, z drugiej strony, pozwala na mieszanie klientów generycznych z bibliotekami nie-generycznymi. Innymi słowy: kod, który został skompilowany w Javie 5, nadal może być wdrożony w Javie 1.4.

Microsoft postanowił jednak zerwać wsteczną kompatybilność dla leków generycznych. to Dlaczego. NET Generics są "lepsze" niż Java Leki generyczne.

Oczywiście, Sun nie są idiotami ani tchórzami. Powodem, dla którego "stchórzyli", było to, że Java była znacznie starsza i bardziej rozpowszechniona niż.NET, kiedy wprowadzili generyki. (Zostały one wprowadzone mniej więcej w tym samym czasie w obu światach.) Złamanie kompatybilności wstecznej byłoby ogromnym bólem.

Inaczej mówiąc: w Javie Generyki są częścią języka (co oznacza, że stosują tylko do Javy, a nie do innych języków), w. Net są są częścią maszyny Wirtualnej (co oznacza, że mają zastosowanie do wszystkich języków , nie tylko C# i Visual Basic.NET).

Porównaj to z funkcjami. NET, takimi jak LINQ, wyrażenia lambda, wnioskowanie typów zmiennych lokalnych, typy anonimowe i drzewa wyrażeń: są to wszystkie funkcje języka . Dlatego istnieją subtelne różnice między VB.NET i C#: gdyby te funkcje były częścią maszyny wirtualnej, byłyby takie same w wszystkich językach. Ale CLR się nie zmienił: nadal jest tak samo w. NET 3.5 SP1 jak w. NET 2.0. Możesz skompilować program C#, który używa LINQ z kompilatorem. NET 3.5 i nadal uruchamiać go na. Net 2.0, pod warunkiem, że nie używasz żadnych bibliotek. NET 3.5. To by nie działało z generics i.NET 1.1, ale działałoby z Javą i Javą 1.4.

 18
Author: Jörg W Mittag,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-29 01:08:19

Kontynuacja mojego poprzedniego postu.

Szablony są jednym z głównych powodów, dla których C++ tak fatalnie zawodzi w intellisense, niezależnie od używanego IDE. Ze względu na specjalizację szablonu, IDE nigdy nie może być naprawdę pewien, czy dany członek istnieje, czy nie. Consider:
template <typename T>
struct X {
    void foo() { }
};

template <>
struct X<int> { };

typedef int my_int_type;

X<my_int_type> a;
a.|

Teraz, kursor znajduje się we wskazanej pozycji i cholernie trudno jest IDE powiedzieć w tym momencie, czy, i co, członkowie a mA. Dla innych języków parsowanie byłoby proste, ale dla C++, konieczna jest wcześniejsza ocena.

Jest jeszcze gorzej. Co by było, gdyby my_int_type były zdefiniowane również wewnątrz szablonu klasy? Teraz jego typ będzie zależał od innego argumentu typu. A tu nawet Kompilatory zawodzą.
template <typename T>
struct Y {
    typedef T my_type;
};

X<Y<int>::my_type> b;

Po odrobinie zastanowienia programista doszedłby do wniosku, że ten kod jest taki sam jak powyższy: Y<int>::my_type rozwiązuje się na int, zatem b powinien być tego samego typu co a, prawda?

Źle. W momencie, gdy kompilator próbuje rozwiązać ten oświadczenie, to faktycznie nie wie Y<int>::my_type jeszcze! Dlatego nie wie, że jest to typ. Może to być coś innego, np. funkcja członka lub pole. Może to powodować niejasności (choć nie w tym przypadku), dlatego kompilator zawiedzie. Musimy powiedzieć wprost, że odwołujemy się do nazwy typu:

X<typename Y<int>::my_type> b;

Teraz Kod się kompiluje. Aby zobaczyć, jak niejasności wynikają z tej sytuacji, rozważ następujący kod:

Y<int>::my_type(123);

To stwierdzenie kodu jest doskonale valid i każe C++ wykonać wywołanie funkcji do Y<int>::my_type. Jednakże, jeśli my_type nie jest funkcją, a raczej typem, to stwierdzenie to nadal będzie ważne i wykona specjalny rzut (rzut w stylu funkcji), który często jest wywołaniem konstruktora. Kompilator nie wie, co mamy na myśli, więc musimy tutaj disambiguate.

 14
Author: Konrad Rudolph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-01 09:21:04

Zarówno Java, jak i C# wprowadziły generyki po pierwszym wydaniu języka. Istnieją jednak różnice w tym, jak podstawowe biblioteki zmieniły się po wprowadzeniu generics. generyczne C#to nie tylko magia kompilatora , więc nie było możliweuogólnienie istniejących klas bibliotecznych bez zerwania wstecznej kompatybilności.

Na przykład w Javie istniejący Framework Collections był całkowicie generyzowany. Java nie ma zarówno generic i legacy non-generic wersja klas kolekcji. pod pewnymi względami jest to o wiele czystsze - jeśli potrzebujesz użyć kolekcji w C#, naprawdę nie ma powodu, aby korzystać z niestandardowej wersji, ale te starsze klasy pozostają na swoim miejscu, zaśmiecając krajobraz.

Inną zauważalną różnicą są klasy Enum w Javie i C#. Enum Javy ma tę nieco krętą definicję:

//  java.lang.Enum Definition in Java
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {

(patrz Angelika Langer ' s very clear wyjaśnienie, dlaczego tak jest. Zasadniczo oznacza to, że Java może nadać typowi bezpieczny dostęp z ciągu znaków do jego wartości Enum:

//  Parsing String to Enum in Java
Colour colour = Colour.valueOf("RED");

Porównaj to z wersją C#:

//  Parsing String to Enum in C#
Colour colour = (Colour)Enum.Parse(typeof(Colour), "RED");

Ponieważ Enum istniało już w C# Zanim generics został wprowadzony do języka, definicja nie mogła się zmienić bez złamania istniejącego kodu. Tak więc, podobnie jak zbiory, pozostaje w podstawowych bibliotekach w tym stanie Dziedzictwa.

 6
Author: serg10,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-30 00:50:23

11 miesięcy spóźnienia, ale myślę, że to pytanie jest gotowe na niektóre Java Wildcard rzeczy.

Jest to składniowa cecha języka Java. Załóżmy, że masz metodę:

public <T> void Foo(Collection<T> thing)

I załóżmy, że nie musisz odwoływać się do typu T w ciele metody. Deklarujesz nazwę T i używasz jej tylko raz, więc po co wymyślać dla niej nazwę? Zamiast tego możesz napisać:

public void Foo(Collection<?> thing)

Znak zapytania prosi kompilator, aby udawał, że zadeklarowałeś normalny parametr o nazwie type to musi pojawić się tylko raz w tym miejscu.

Nie ma nic, co można zrobić z symbolami wieloznacznymi, czego nie można również zrobić z parametrem nazwanego typu (czyli jak te rzeczy są zawsze robione w C++ i C#).

 4
Author: Daniel Earwicker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-10 13:49:57

Wikipedia ma świetne wyniki porównujące zarówno szablony Java/C# generics i Java generics/C++. Główny artykuł o generykach wydaje się nieco zaśmiecony, ale ma w sobie dobre informacje.

 2
Author: travis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-28 05:14:19

Największą reklamacją jest usunięcie typu. W tym przypadku generyki nie są egzekwowane w czasie wykonywania. Oto link do niektórych dokumentów Sun na temat.

Generyki są zaimplementowane według typu kasowanie: ogólne informacje o typie to obecny tylko w czasie kompilacji, po który jest usuwany przez kompilator.

 1
Author: Matt Cummings,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-28 05:15:45

Szablony C++ są w rzeczywistości znacznie potężniejsze niż ich odpowiedniki C# i Java, ponieważ są oceniane w czasie kompilacji i specjalizacji wsparcia. Pozwala to na Meta-Programowanie szablonów i sprawia, że kompilator C++ jest odpowiednikiem maszyny Turinga (tzn. podczas procesu kompilacji można obliczyć wszystko, co można obliczyć za pomocą maszyny Turinga).

 1
Author: On Freund,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-28 06:32:49

W Javie generyki są tylko na poziomie kompilatora, więc otrzymujesz:

a = new ArrayList<String>()
a.getClass() => ArrayList

Zauważ, że typ ' a ' jest listą tablic, a nie listą łańcuchów. Typ więc listy bananów byłby równy () liście małp.

Że tak powiem.
 1
Author: izb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-28 07:22:31

Wygląda na to, że wśród innych bardzo ciekawych propozycji jest jedna o dopracowaniu generyków i przełamaniu kompatybilności wstecznej:

Obecnie zaimplementowane są generyki za pomocą kasowania, co oznacza, że generic type information is not dostępne w czasie wykonywania, co sprawia, że niektóre kod trudny do napisania. Generics zostały wdrożone w ten sposób, aby wspierać kompatybilność wsteczna ze starszymi kod niestandardowy. Reified generics uczyniłoby Typ generyczny informacje dostępne w czasie wykonywania, który złamałby dziedzictwo nie-generyczne kod. Jednak Neal Gafter ma proponowane typy making reifiable only jeśli jest określony, tak aby nie złamać kompatybilność wsteczna.

At Artykuł Alexa Millera o Javie 7

 1
Author: pek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-23 07:36:09

NB: Nie mam wystarczająco dużo uwagi, aby skomentować, więc nie krępuj się przenieść to jako komentarz do odpowiedniej odpowiedzi.

Wbrew powszechnemu przekonaniu, którego nigdy nie rozumiem, skąd się wzięła,. NET zaimplementował prawdziwe generyki bez łamania wstecznej kompatybilności i poświęcili na to wyraźny wysiłek. Nie musisz zmieniać niestandardowego kodu.NET 1.0 na generics, aby być używanym w. Net 2.0. Zarówno listy generyczne, jak i nie generyczne są nadal dostępne w. Net framework 2.0 nawet do 4.0, dokładnie z powodu tylko wstecznej kompatybilności. Dlatego stare kody, które nadal używały nie-generycznej ArrayList, nadal będą działać i będą używać tej samej klasy ArrayList, co wcześniej. Kompatybilność kodu wstecznego jest zawsze utrzymywana od wersji 1.0 do teraz... Więc nawet w. Net 4.0, nadal musisz wybrać opcję, aby użyć dowolnej klasy non-generics z 1.0 BCL, jeśli zdecydujesz się to zrobić.

Więc nie sądzę, aby java musiała łamać wsteczną kompatybilność, aby wspierać prawdziwe generyki.

 0
Author: Sheepy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-03 18:20:58