Jakie są różnice między Generics w C # i Java… a Szablony w C ++? [Zamknięte]

203

Najczęściej używam Javy, a generyczne są stosunkowo nowe. Wciąż czytam, że Java podjęła błędną decyzję lub że .NET ma lepsze implementacje itp. Itp.

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

pek
źródło

Odpowiedzi:

364

Dodam mój głos do hałasu i postaram się wyjaśnić:

C # Generics pozwala zadeklarować coś takiego.

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

a wtedy kompilator zapobiegnie umieszczaniu rzeczy, których nie ma Personna liście.
Za kulisami kompilator C # po prostu umieszcza List<Person>w pliku dll .NET, ale w czasie wykonywania kompilator JIT idzie i buduje nowy zestaw kodu, tak jakbyś napisał specjalną klasę list tylko do przechowywania ludzi - coś w rodzaju ListOfPerson.

Zaletą tego jest to, że sprawia, że ​​jest naprawdę szybki. Nie ma rzutowania ani żadnych innych rzeczy, a ponieważ dll zawiera informacje, że jest to lista Person, inny kod, który patrzy na nią później przy użyciu refleksji, może stwierdzić, że zawiera Personobiekty (więc otrzymujesz inteligencję i tak dalej).

Minusem tego jest to, że stary kod C # 1.0 i 1.1 (przed dodaniem generycznych) nie rozumie tych nowych List<something>, więc musisz ręcznie przekonwertować rzeczy z powrotem na zwykły stary, Listaby z nimi współpracować. Nie stanowi to większego problemu, ponieważ kod binarny C # 2.0 nie jest kompatybilny wstecz. Jedyny raz to się stanie, jeśli 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 jest w pewnym sensie. Kompilator zapobiegnie również umieszczaniu rzeczy, których nie ma Personna liście.

Różnica polega na tym, co dzieje się za kulisami. W przeciwieństwie do C #, Java nie buduje specjalnej wersji ListOfPerson- po prostu używa zwykłego starego, ArrayListktóry zawsze był w Javie. Kiedy wyciągniesz rzeczy z szeregu, Person p = (Person)foo.get(1);nadal musisz wykonać zwykły taniec castingowy. Kompilator oszczędza ci naciskania klawiszy, ale szybkość trafienia / rzucania jest wciąż zwiększana, jak zawsze.
Kiedy ludzie wspominają o „skasowaniu typu”, właśnie o tym mówią. Kompilator wstawia dla Ciebie rzutowania, a następnie „kasuje” fakt, że ma to być lista Personnie tylkoObject

Zaletą tego podejścia jest to, że stary kod, który nie rozumie generycznych, nie musi się tym przejmować. Nadal ma do czynienia z tym samym starym, ArrayListco zawsze. Jest to ważniejsze w świecie Java, ponieważ chcieli obsługiwać kompilowanie kodu przy użyciu języka Java 5 z ogólnymi wersjami i uruchamianie go na starej wersji JVM 1.4 lub wcześniejszej, z którą firma Microsoft celowo postanowiła nie zawracać sobie głowy.

Minusem jest szybkość, o której wspominałem wcześniej, a także dlatego, że nie ma ListOfPersonpseudoklasy lub czegoś podobnego wchodzącego do plików .class, kodu, który patrzy na to później (z refleksją lub jeśli wyciągniesz go z innej kolekcji gdzie został przekształcony Objectitp.) nie może w żaden sposób powiedzieć, że ma to być lista zawierająca tylko, Persona nie tylko dowolną inną listę tablic.

Szablony C ++ pozwalają zadeklarować coś takiego

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

Wygląda jak C # i Java ogólne i zrobi to, co według ciebie powinno być, ale za kulisami dzieją się różne rzeczy.

Ma to najwięcej wspólnego z generycznymi C #, ponieważ tworzy specjalne, pseudo-classesa nie tylko wyrzuca informacje o typie, jak java, ale jest to zupełnie inny czajnik ryb.

Zarówno C #, jak i Java produkują dane wyjściowe przeznaczone dla maszyn wirtualnych. Jeśli napiszesz kod zawierający Personklasę, w obu przypadkach pewne informacje o Personklasie przejdą do pliku .dll lub .class, a JVM / CLR zrobi z tym różne rzeczy.

C ++ produkuje surowy kod binarny x86. Wszystko nie jest przedmiotem i nie ma żadnej maszyny wirtualnej, która musiałaby wiedzieć o Personklasie. Nie ma żadnego boksowania ani rozpakowywania, a funkcje nie muszą należeć do klas ani niczego.

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

W języku C # i Javie system ogólny musi wiedzieć, jakie metody są dostępne dla klasy, i musi przekazać to do maszyny wirtualnej. Jedynym sposobem, aby to powiedzieć, jest albo na stałe zakodować rzeczywistą klasę, albo przy użyciu interfejsów. Na przykład:

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

Ten kod nie będzie się kompilował w języku C # ani Javie, ponieważ nie wie, że ten typ Tfaktycznie udostępnia metodę o nazwie Name (). Musisz to powiedzieć - w C # jak to:

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

A potem musisz się upewnić, że to, co przekazujesz do addNames, implementuje interfejs IHasName i tak dalej. Składnia Java jest inna ( <T extends IHasName>), ale ma te same problemy.

„Klasycznym” przypadkiem tego problemu jest próba napisania funkcji, która to robi

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

Nie można tak naprawdę napisać tego kodu, ponieważ nie ma możliwości zadeklarowania interfejsu za pomocą +zawartej w nim metody. Przegrywasz

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

Więc masz to :-)

Orion Edwards
źródło
8
Wygenerowana pseudoklasa dla typów referencji w języku C # ma tę samą implementację, więc nie uzyskasz dokładnie ListOfPeople. Sprawdź blogs.msdn.com/ericlippert/archive/2009/07/30/…
Piotr Czapla
4
Nie można nie skompilować Java Kod 5 przy użyciu rodzajowych i go uruchomić na starym 1.4 VM (przynajmniej Sun JDK nie realizuje tego. Niektóre 3rd strona narzędzia zrobić). Co można zrobić, to użycie uprzednio skompilowane 1,4 JAR z Kod 1,5 / 1,6.
finnw
4
Sprzeciwiam się stwierdzeniu, że nie można pisać int addNames<T>( T first, T second ) { return first + second; }w języku C #. Typ ogólny może być ograniczony do klasy zamiast interfejsu, a istnieje sposób zadeklarowania klasy za pomocą +operatora.
Mashmagar,
4
@AlexanderMalakhov celowo nie jest idiomatyczny. Nie chodziło o edukację na temat idiomów C ++, ale o zilustrowanie, w jaki sposób ten sam wyglądający fragment kodu jest traktowany inaczej w każdym języku. Osiągnięcie tego celu byłoby trudniejsze, gdyby kod wyglądał inaczej
Orion Edwards,
3
@ phresnel Zgadzam się co do zasady, ale gdybym napisał ten fragment w idiomatycznym C ++, byłoby to znacznie mniej zrozumiałe dla programistów C # / Java, a zatem (sądzę) wykonałby gorszą robotę, tłumacząc różnicę. Zgódźmy się nie zgadzać w tej sprawie :-)
Orion Edwards
61

C ++ rzadko używa terminologii „ogólna”. Zamiast tego używa się słowa „szablony” i jest ono bardziej dokładne. Szablony opisują jedną technikę pozwalającą 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 tylko dopuszczają argumenty typu czas kompilacji, ale także argumenty stałej wartości kompilacji: szablony mogą być podawane jako liczby całkowite, a nawet podpisy funkcji. Oznacza to, że w czasie kompilacji możesz wykonywać dość funky, 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, productktóry ma jeden argument wartości. Definiuje także specjalizację dla tego szablonu, która jest używana za każdym razem, gdy argument ma wartość 1. Pozwala mi to zdefiniować rekurencję w stosunku do definicji szablonu. Wierzę, że po raz pierwszy odkrył to Andrei 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, chociaż jest to pożądane, wszystkie typy nie mogą być traktowane jednakowo w ramach implementacji. Szablony C ++ uwzględniają to. Jest to ta sama różnica, jaką OOP robi między interfejsem a implementacją z przesłonięciem metod wirtualnych.

Szablony C ++ są niezbędne dla paradygmatu programowania algorytmicznego. Na przykład prawie wszystkie algorytmy dla kontenerów są zdefiniowane jako funkcje, które akceptują typ kontenera jako typ szablonu i traktują je jednolicie. W rzeczywistości nie jest to w porządku: C ++ nie działa na kontenerach, ale raczej na zakresach zdefiniowanych przez dwa iteratory, wskazujących początek i koniec kontenera. Zatem cała treść jest ograniczona przez iteratory: początek <= elementy <koniec.

Korzystanie z iteratorów zamiast kontenerów jest przydatne, ponieważ pozwala operować na częściach kontenera zamiast na całości.

Kolejną cechą wyróżniającą C ++ jest możliwość częściowej specjalizacji szablonów klas. Jest to nieco związane z dopasowaniem wzorca argumentów w języku Haskell i innych językach funkcjonalnych. Rozważmy na przykład klasę, która przechowuje elementy:

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

Działa to dla każdego typu elementu. Powiedzmy jednak, że możemy przechowywać wskaźniki bardziej efektywnie niż inne typy, stosując specjalną sztuczkę. Możemy to zrobić częściowo specjalizując się we wszystkich typach wskaźników:

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

Teraz, ilekroć wystąpimy 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*.
Konrad Rudolph
źródło
Czasami żałowałem, że funkcja ogólna w .net nie pozwala na używanie innych niż typy jako kluczy. Gdyby tablice typu wartości były częścią Framework (dziwię się, że nie są one w pewien sposób, biorąc pod uwagę potrzebę interakcji ze starszymi interfejsami API, które osadzają tablice o stałej wielkości w strukturach), przydatne byłoby zadeklarowanie klasa, która zawierała kilka pojedynczych elementów, a następnie tablicę typu wartości, której rozmiar był parametrem ogólnym. W tej chwili najbliższym rozwiązaniem jest posiadanie obiektu klasy, który przechowuje poszczególne elementy, a następnie zawiera odniesienie do osobnego obiektu zawierającego tablicę.
supercat
@ superuper Jeśli wchodzisz w interakcję ze starszym interfejsem API, pomysł polega na użyciu marshallingu (który może być opatrzony adnotacjami za pomocą atrybutów). CLR i tak nie ma tablic o stałych rozmiarach, więc argumenty szablonu innego niż typ nie byłyby tutaj pomocne.
Konrad Rudolph
Wydaje mi się, że zastanawiające jest to, że wydaje się, że posiadanie tablic typów wartości o stałej wielkości nie powinno być trudne i pozwoliłoby na zestawianie wielu typów danych przez odniesienie, a nie przez wartość. Podczas gdy marshal-by-value może być użyteczny w przypadkach, których naprawdę nie da się załatwić w żaden inny sposób, uważam, że marshal-by-ref jest lepszy w prawie wszystkich przypadkach, w których jest użyteczny, więc pozwalając na takie przypadki zawierać struktury ze stałym tablice o rozmiarach wydawałyby się przydatną funkcją.
supercat
BTW, inna sytuacja, w której przydatne byłyby parametry ogólne nietypowe, dotyczyłaby typów danych reprezentujących wielkości wymiarowe. Można uwzględnić informacje o wymiarach w instancjach reprezentujących wielkości, ale posiadanie takich informacji w typie pozwoliłoby na określenie, że kolekcja ma pomieścić obiekty reprezentujące konkretną zwymiarowaną jednostkę.
supercat
35

Anders Hejlsberg sam opisał tutaj różnice „ Generics w C #, Java i C ++ ”.

jfs
źródło
bardzo podoba mi się ten wywiad. to wyjaśnia, dla facetów spoza c #, takich jak ja, co dzieje się z c # generics.
Johannes Schaub - litb
18

Istnieje już wiele pozytywnych odpowiedzi na jakie różnice są, więc podam nieco innej perspektywy i dodać dlaczego .

Jak już wyjaśniono, główną różnicą jest usuwanie typu , tj. Fakt, że kompilator Java usuwa typy ogólne i nie kończą się one w wygenerowanym kodzie bajtowym. Pytanie jednak brzmi: dlaczego ktokolwiek miałby to zrobić? To nie ma sensu! A może to?

Jaka jest alternatywa? Jeśli nie zaimplementować rodzajowych w języku, w którym należy je wdrożyć? Odpowiedź brzmi: w maszynie wirtualnej. Co psuje kompatybilność wsteczną.

Z drugiej strony, usuwanie typu pozwala mieszać ogólnych klientów z bibliotekami ogólnymi. Innymi słowy: kod skompilowany na Javie 5 nadal można wdrożyć w Javie 1.4.

Microsoft postanowił jednak zerwać wsteczną kompatybilność dla leków generycznych. To dlaczego .NET Generics są „lepsze” niż Java rodzajowych.

Oczywiście, że Słońce nie jest idiotą ani tchórzem. Powodem, dla którego „stchórzyli”, było to, że Java była znacznie starsza i bardziej rozpowszechniona niż .NET, kiedy wprowadzono generyczne. (Zostały wprowadzone mniej więcej w tym samym czasie w obu światach.) Zerwanie wstecznej kompatybilności byłoby ogromnym bólem.

Mówiąc jeszcze inaczej: w Javie, Generics są częścią Języka (co oznacza, że ​​dotyczą tylko Javy, a nie innych języków), w .NET są częścią Maszyny Wirtualnej (co oznacza, że ​​dotyczą wszystkich języków, a nie tylko C # i Visual Basic.NET).

Porównaj to z funkcjami .NET, takimi jak LINQ, wyrażenia lambda, lokalne wnioskowanie o typach zmiennych, typy anonimowe i drzewa wyrażeń: wszystkie są funkcjami językowymi . Właśnie dlatego istnieją subtelne różnice między VB.NET i C #: gdyby te funkcje były częścią maszyny wirtualnej, byłyby takie same we wszystkich językach. Ale CLR się nie zmienił: nadal jest taki sam w .NET 3.5 SP1 jak w .NET 2.0. Możesz skompilować program w języku C #, który używa LINQ z kompilatorem .NET 3.5 i nadal uruchamiać go w .NET 2.0, pod warunkiem, że nie używasz żadnych bibliotek .NET 3.5. Że nie praca z rodzajowych i .NET 1.1, ale to będzie działać z Java i Java 1.4.

Jörg W Mittag
źródło
3
LINQ jest przede wszystkim funkcją biblioteki (chociaż C # i VB dodały również cukier składniowy). Każdy język, który jest ukierunkowany na 2.0 CLR, może w pełni korzystać z LINQ, po prostu ładując zestaw System.Core.
Richard Berg
Tak, przepraszam, powinienem był bardziej wyrazisty wrt. LINQ. Miałem na myśli składnię zapytania, a nie monadyczne standardowe operatory zapytania, metody rozszerzenia LINQ lub interfejs IQueryable. Oczywiście możesz korzystać z nich z dowolnego języka .NET.
Jörg W Mittag
1
Myślę o innej opcji dla Java. Nawet Oracle nie chce zerwać z poprzednimi wersjami kompatybilności, mogą jednak zrobić pewien kompilator, aby uniknąć usunięcia informacji o typie. Na przykład ArrayList<T>może być emitowany jako nowy wewnętrznie nazwany typ z (ukrytym) Class<T>polem statycznym . Tak długo, jak nowa wersja ogólnej biblioteki lib zostanie wdrożona z kodem bajtu 1.5+, będzie mogła działać na JVM 1.4-JVM.
Earth Engine
14

Kontynuacja mojego poprzedniego postu.

Szablony są jednym z głównych powodów, dla których C ++ zawodzi tak beznadziejnie w intellisense, niezależnie od używanego IDE. Ze względu na specjalizację szablonów IDE nigdy nie może być naprawdę pewien, czy dany element istnieje, czy nie. Rozważać:

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 IDE w tym momencie trudno powiedzieć, czy i co amają członkowie . W przypadku innych języków parsowanie byłoby proste, ale w przypadku C ++ potrzebna jest wcześniej sporo oceny.

Pogarsza się. Co jeśli my_int_typezostałyby zdefiniowane również w szablonie klasy? Teraz jego typ będzie zależał od innego argumentu typu. I tutaj zawodzą nawet kompilatory.

template <typename T>
struct Y {
    typedef T my_type;
};

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

Po krótkiej refleksji programista doszedł do wniosku, że ten kod jest taki sam jak powyżej: Y<int>::my_typepostanawia int, dlatego bpowinien być tego samego typu co a, prawda?

Źle. W momencie, gdy kompilator próbuje rozwiązać tę instrukcję, tak naprawdę Y<int>::my_typejeszcze nie wie ! 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 wyraźnie powiedzieć, że odwołujemy się do nazwy typu:

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

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

Y<int>::my_type(123);

Ta instrukcja kodu jest całkowicie poprawna i mówi C ++, aby wykonała wywołanie funkcji Y<int>::my_type. Jeśli jednak my_typenie jest funkcją, ale typem, to instrukcja nadal byłaby ważna i wykonałaby rzut specjalny (rzutowanie w stylu funkcji), który często jest wywołaniem konstruktora. Kompilator nie może powiedzieć, co mamy na myśli, więc musimy tutaj ujednoznacznić.

Konrad Rudolph
źródło
2
Całkiem się zgadzam. Jest jednak nadzieja. System autouzupełniania i kompilator C ++ muszą ze sobą ściśle współpracować. Jestem pewien, że Visual Studio nigdy nie będzie miało takiej funkcji, ale może się zdarzyć w Eclipse / CDT lub innym IDE opartym na GCC. NADZIEJA ! :)
Benoît
6

Zarówno Java, jak i C # wprowadziły generyczne po ich pierwszej wersji językowej. Istnieją jednak różnice w tym, jak zmieniły się biblioteki podstawowe, gdy wprowadzono generyczne. Generyczne C # to nie tylko magia kompilatora, więc nie było możliwe wygenerowanie istniejących klas bibliotek bez naruszenia kompatybilności wstecznej.

Na przykład w Javie istniejący Framework Kolekcje został całkowicie uogólniony . Java nie ma zarówno ogólnej, jak i starszej nie-ogólnej wersji klas kolekcji. Pod pewnymi względami jest to o wiele czystsze - jeśli chcesz użyć kolekcji w języku C #, naprawdę nie ma powodu, aby iść z wersją inną niż ogólna, ale te starsze klasy pozostają na miejscu, zagracając krajobraz.

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

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

(zobacz bardzo jasne wyjaśnienie Angeliki Langer , dlaczego tak jest. Zasadniczo oznacza to, że Java może zapewnić typowi bezpieczny dostęp od łańcucha do 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 języku C # przed wprowadzeniem do języka generics, definicja nie mogła się zmienić bez zerwania istniejącego kodu. Podobnie jak kolekcje, pozostaje on w podstawowych bibliotekach w tym starszym stanie.

serg10
źródło
Nawet generyczne C # to nie tylko magia kompilatora, kompilator może wykonać dalszą magię, aby wygenerować istniejącą bibliotekę. Nie ma powodu, dlaczego muszą zmienić nazwę ArrayListna List<T>i umieścić go w nowej przestrzeni nazw. Faktem jest, że jeśli w kodzie źródłowym pojawi się klasa, ponieważ ArrayList<T>będzie ona inną nazwą klasy generowanej przez kompilator w kodzie IL, więc nie może wystąpić konflikt nazw.
Earth Engine
4

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

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

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

Załóżmy, że nie musisz odwoływać się do typu T w treści metody. Podajesz nazwę T, a następnie używasz jej tylko raz, więc dlaczego miałbyś wymyślić jej nazwę? Zamiast tego możesz napisać:

public void Foo(Collection<?> thing)

Znak zapytania prosi kompilator o udawanie, że zadeklarowałeś normalny parametr nazwanego typu, który musi pojawić się tylko raz w tym miejscu.

Nic nie można zrobić z symbolami wieloznacznymi, czego nie można zrobić również z parametrem nazwanego typu (tak właśnie dzieje się w C ++ i C #).

Daniel Earwicker
źródło
2
Kolejne 11 miesięcy spóźnienia ... Są rzeczy, które możesz zrobić z symbolami wieloznacznymi Java, których nie możesz zrobić z parametrami nazwanego typu. Możesz to zrobić w Javie: class Foo<T extends List<?>>i użyć, Foo<StringList>ale w C # musisz dodać ten dodatkowy parametr typu: class Foo<T, T2> where T : IList<T2>i użyć niezgrabnego Foo<StringList, String>.
R. Martinho Fernandes
1

Największą skargą jest usunięcie typu. W związku z tym generyczne nie są wymuszane w czasie wykonywania. Oto link do niektórych dokumentów firmy Sun na ten temat .

Generyczne są implementowane przez kasowanie typu: ogólne informacje o typie są obecne tylko w czasie kompilacji, po czym są usuwane przez kompilator.

Matt Cummings
źródło
1

Szablony C ++ są w rzeczywistości znacznie potężniejsze niż ich odpowiedniki w języku C # i Java, ponieważ są oceniane podczas kompilacji i obsługują specjalizację. Pozwala to na Meta-Programowanie Szablonów i sprawia, że ​​kompilator C ++ jest równoważny maszynie Turinga (tzn. Podczas procesu kompilacji można obliczyć wszystko, co można obliczyć za pomocą maszyny Turinga).

Na Freund
źródło
1

W Javie ogólne są tylko na poziomie kompilatora, więc otrzymujesz:

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

Zauważ, że typ „a” to lista tablic, a nie lista ciągów. Zatem typ listy bananów byłby równy () liście małp.

Że tak powiem.

izb
źródło
1

Wygląda na to, że pośród innych bardzo interesujących propozycji jest jedna dotycząca udoskonalenia generycznych i zerwania z kompatybilnością wsteczną:

Obecnie generyczne są implementowane przy użyciu skasowania, co oznacza, że ​​ogólne informacje o typie nie są dostępne w czasie wykonywania, co utrudnia napisanie pewnego rodzaju kodu. Generyczne zostały zaimplementowane w ten sposób, aby wspierać wsteczną kompatybilność ze starszym nie-ogólnym kodem. Reified generics udostępniłby informacje o typie ogólnym w czasie wykonywania, co złamałoby starszy nie-ogólny kod. Jednak Neal Gafter zaproponował, aby typy były możliwe do ponownego uruchomienia tylko wtedy, gdy jest to określone, aby nie naruszyć kompatybilności wstecznej.

w artykule Alexa Millera na temat propozycji Java 7

pek
źródło
0

Uwaga: Nie mam wystarczającego sensu, aby komentować, więc możesz przesłać to jako komentarz do odpowiedniej odpowiedzi.

Wbrew powszechnemu przekonaniu, którego nigdy nie rozumiem, skąd się wziął. Net zaimplementował prawdziwe generyczne produkty bez naruszania wstecznej kompatybilności i dołożył do tego wyraźnego wysiłku. Nie musisz zamieniać nieogólnego kodu .net 1.0 na ogólne, aby używać go w .net 2.0. Zarówno lista ogólna, jak i ogólna są nadal dostępne w .Net Framework 2.0, nawet do wersji 4.0, właśnie z tego powodu, tylko ze względu na kompatybilność wsteczną. Dlatego stare kody, które nadal używały nietypowej ArrayList, będą nadal działać i będą używać tej samej klasy ArrayList, jak poprzednio. Kompatybilność z kodem wstecznym jest zawsze utrzymywana od wersji 1.0 do teraz ... Więc nawet w .net 4.0 nadal musisz wybrać dowolną klasę inną niż ogólna od 1.0 BCL, jeśli zdecydujesz się to zrobić.

Więc nie sądzę, że java musi zerwać wsteczną kompatybilność, aby obsługiwać prawdziwe generyczne.

Owcze
źródło
Ludzie nie mówią o takiej kompatybilności wstecznej. Chodzi o zgodność wsteczną dla środowiska wykonawczego : kodu napisanego przy użyciu generycznych w .NET 2.0 nie można uruchomić na starszych wersjach .NET Framework / CLR. Podobnie, jeśli Java miałaby wprowadzić „prawdziwe” generyczne, nowszy kod Java nie byłby w stanie działać na starszych JVM (ponieważ wymaga to przerwania zmian w kodzie bajtowym).
tzaman
To jest .net, nie ogólne. Zawsze wymaga ponownej kompilacji, aby dotrzeć do określonej wersji CLR. Istnieje kompatybilność kodu bajtowego, zgodność kodu. A także odpowiadałem konkretnie na potrzebę konwersji starego kodu, który używał starej listy, aby użyć nowej listy generycznej, co wcale nie jest prawdą.
Sheepy
1
Myślę, że ludzie mówią o kompatybilności z przodu . To znaczy kod .net 2.0 do uruchomienia na .net 1.1, który się zepsuje, ponieważ środowisko wykonawcze 1.1 nie wie nic o „pseudoklasie” 2.0. Czy nie powinno być tak, że „java nie implementuje true, ponieważ chce zachować zgodność z poprzednimi wersjami”? (zamiast wstecz)
Sheepy
Problemy z kompatybilnością są subtelne. Nie sądzę, że problem polegał na tym, że dodanie „prawdziwych” rodzajów ogólnych do Javy wpłynęłoby na wszystkie programy korzystające ze starszych wersji języka Java, ale raczej kod, który używał „nowych ulepszonych” elementów ogólnych, miałby trudności z wymianą takich obiektów ze starszym kodem, który nic nie wiedział o nowych typach. Załóżmy na przykład, że program ma taki, ArrayList<Foo>który chce przekazać starszej metodzie, która ma wypełnić ArrayListinstancje Foo. Jeśli ArrayList<foo>nie jest ArrayList, jak to działa?
supercat