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>();
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>();
Odpowiedzi:
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.
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.
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.
Kolekcje są zdefiniowane tak, aby wymagały typu, który pochodzi java.lang.Object
. Typy podstawowe po prostu tego nie robią.
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.
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.