Dlaczego Java Generics nie obsługuje typów pierwotnych?

236

Dlaczego produkty generyczne w Javie działają z klasami, ale nie z typami pierwotnymi?

Na przykład działa to dobrze:

List<Integer> foo = new ArrayList<Integer>();

ale nie jest to dozwolone:

List<int> bar = new ArrayList<int>();
sgokhales
źródło
1
int i = (int) new Object (); kompiluje się dobrze.
Sachin Verma

Odpowiedzi:

243

Generics w Javie jest konstrukcją całkowicie kompilującą się w czasie kompilacji - kompilator zamienia wszystkie ogólne zastosowania w rzutowania na odpowiedni typ. Ma to na celu zachowanie zgodności z poprzednimi wersjami środowiska wykonawczego JVM.

To:

List<ClassA> list = new ArrayList<ClassA>();
list.add(new ClassA());
ClassA a = list.get(0);

zamienia się w (z grubsza):

List list = new ArrayList();
list.add(new ClassA());
ClassA a = (ClassA)list.get(0);

Zatem wszystko, co jest używane jako ogólne, musi być konwertowane na Object (w tym przykładzie get(0)zwraca an Object), a typy pierwotne nie są. Dlatego nie można ich używać w ogólnych.

Thecoop
źródło
8
@DanyalAytekin - Tak naprawdę, generyczne Java nie są obsługiwane jak szablony C ++ w ogóle ...
Stephen C
20
Dlaczego kompilator Java nie może również umieścić pierwotnego typu przed użyciem? To powinno być możliwe, prawda?
vrwim
13
@vrwim - To może być możliwe. Ale to byłby po prostu cukier składniowy. Prawdziwy problem polega na tym, że generyczne Java z pudełkowymi operacjami pierwotnymi są stosunkowo drogie zarówno pod względem czasu, jak i przestrzeni w porównaniu z modelem C ++ / C # ... gdzie używany jest rzeczywisty typ pierwotny.
Stephen C,
6
@MauganRa tak, wiem, że mogę :) Stoję przy mym stanowisku, że to okropny projekt. Mam nadzieję, że zostanie to naprawione w java 10 (a przynajmniej tak słyszałem), a także funkcje wyższego rzędu. Nie cytuj mnie o tym.
Ced
4
@Ced w pełni zgadza się, że jest to zły projekt, który bez końca szkodzi początkującym i profesjonalistom
MauganRa
37

W Javie, generyczne działają w taki sposób, w jaki działają ... przynajmniej częściowo ... ponieważ zostały dodane do języka wiele lat po zaprojektowaniu języka 1 . Projektanci języków mieli ograniczone opcje generyczne, ponieważ musieli wymyślić projekt, który byłby wstecznie zgodny z istniejącym językiem i biblioteką klas Java .

Inne języki programowania (np. C ++, C #, Ada) pozwalają na stosowanie typów prymitywnych jako typów parametrów w rodzajach ogólnych. Ale drugą stroną tego procesu jest to, że implementacje generycznych (lub typów szablonów) takich języków zazwyczaj pociągają za sobą generowanie odrębnej kopii typu ogólnego dla każdej parametryzacji typu.


1 - Powodem, dla którego generycy nie zostali uwzględnieni w Javie 1.0, była presja czasu. Uważali, że muszą szybko wydać język Java, aby wypełnić nową szansę rynkową oferowaną przez przeglądarki internetowe. James Gosling stwierdził, że wolałby włączyć leki generyczne, gdyby mieli czas. Zgadłby ktoś, jak wyglądałby język Java, gdyby tak się stało.

Stephen C.
źródło
11

W java generyczne są implementowane przy użyciu „Kasowania typu” dla kompatybilności wstecznej. Wszystkie typy ogólne są konwertowane na Object w czasie wykonywania. na przykład,

public class Container<T> {

    private T data;

    public T getData() {
        return data;
    }
}

będą widoczne w czasie wykonywania jako,

public class Container {

    private Object data;

    public Object getData() {
        return data;
    }
}

Kompilator jest odpowiedzialny za zapewnienie odpowiedniej obsady, aby zapewnić bezpieczeństwo typu.

Container<Integer> val = new Container<Integer>();
Integer data = val.getData()

stanie się

Container val = new Container();
Integer data = (Integer) val.getData()

Teraz pytanie brzmi: dlaczego „Obiekt” jest wybierany jako typ w czasie wykonywania?

Odpowiedź brzmi: Obiekt jest nadklasą wszystkich obiektów i może reprezentować dowolny obiekt zdefiniowany przez użytkownika.

Ponieważ wszystkie prymitywy nie dziedziczą po „ Object ”, więc nie możemy używać go jako typu ogólnego.

FYI: Projekt Valhalla próbuje rozwiązać powyższy problem.

Piyush Sagar
źródło
Plus 1 za prawidłowe nazewnictwo.
Drazen Bjelovuk
7

Kolekcje są zdefiniowane tak, aby wymagały typu, który pochodzi java.lang.Object. Typy podstawowe po prostu tego nie robią.

ZeissS
źródło
26
Myślę, że pytanie brzmi „dlaczego”. Dlaczego leki generyczne wymagają obiektów? Konsensus wydaje się być taki, że jest to mniej wybór projektowy, a bardziej sprzyja zgodności wstecznej. Moim zdaniem, jeśli leki generyczne nie są w stanie poradzić sobie z prymitywami, oznacza to deficyt funkcjonalności. Na obecnym etapie wszystko dotyczące prymitywów musi być napisane dla każdego prymitywu: zamiast Komparatora <t, t> mamy Integer.compare (int a, int b), Byte.compare (bajt a, bajt b) itp. To nie jest rozwiązanie!
John P
1
Tak, generyczne w porównaniu z prymitywnymi typami byłoby koniecznością. Oto link do propozycji dotyczącej tego openjdk.java.net/jeps/218
crow
5

Zgodnie z Dokumentacją Java , zmienne typu ogólnego mogą być tworzone tylko z typami referencyjnymi, a nie pierwotnymi.

Ma to nastąpić w Javie 10 w ramach projektu Valhalla .

W artykule Briana Goetza o stanie specjalizacji

Istnieje doskonałe wyjaśnienie przyczyny, dla której rodzajowe nie były obsługiwane dla prymitywnych. I w jaki sposób zostanie zaimplementowany w przyszłych wydaniach Java.

Obecna wymazana implementacja Java, która produkuje jedną klasę dla wszystkich instancji referencyjnych i nie obsługuje prymitywnych instancji. (Jest to tłumaczenie homogeniczne, a ograniczenie, że ogólne elementy języka Java mogą wykraczać poza typy referencyjne, wynika z ograniczeń tłumaczenia jednorodnego w odniesieniu do zestawu kodów bajtowych JVM, który używa różnych kodów bajtowych dla operacji na typach referencyjnych w porównaniu z typami pierwotnymi.) Jednak skasowane rodzaje ogólne w Javie zapewniają zarówno parametryczność behawioralną (metody ogólne), jak i parametryczność danych (instancje typu raw i symboli zastępczych typów ogólnych).

...

wybrano jednorodną strategię translacji, w której zmienne typu ogólnego są usuwane do granic możliwości, gdy są włączane do kodu bajtowego. Oznacza to, że niezależnie od tego, czy klasa jest ogólna, czy nie, nadal kompiluje się do pojedynczej klasy o tej samej nazwie i której podpisy są takie same. Bezpieczeństwo typu jest weryfikowane podczas kompilacji, a środowisko wykonawcze jest ograniczone przez ogólny system typów. To z kolei narzuciło ograniczenie, że generyczne mogą działać tylko nad typami referencyjnymi, ponieważ Object jest najbardziej ogólnym dostępnym typem i nie obejmuje typów pierwotnych.

vinS
źródło
0

Podczas tworzenia obiektu nie można podstawić typu pierwotnego parametrem typu. Z powodu tego ograniczenia jest to problem z implementacją kompilatora. Typy pierwotne mają własne instrukcje kodu bajtowego do ładowania i przechowywania na stosie maszyny wirtualnej. Dlatego nie jest niemożliwe skompilowanie prymitywnych rodzajów ogólnych w tych oddzielnych ścieżkach kodu bajtowego, ale komplikowałoby to kompilator.

Atif
źródło