Jaki jest powód, dla którego Java nam na to nie pozwala
private T[] elements = new T[initialCapacity];
Zrozumiałem, że .NET nie pozwala nam na to, ponieważ w .NET masz typy wartości, które w czasie wykonywania mogą mieć różne rozmiary, ale w Javie wszystkie rodzaje T będą odwołaniami do obiektów, dzięki czemu mają ten sam rozmiar ( Popraw mnie, jeśli się mylę).
Jaki jest powód?
java
generics
type-erasure
pochłonęło elysium
źródło
źródło
Odpowiedzi:
Jest tak, ponieważ tablice Javy (w przeciwieństwie do ogólnych) zawierają w czasie wykonywania informacje o typie swojego komponentu. Musisz więc znać typ komponentu podczas tworzenia tablicy. Ponieważ nie wiesz, co
T
jest w czasie wykonywania, nie możesz utworzyć tablicy.źródło
ArrayList <SomeType>
to się wtedy robi ?new ArrayList<SomeType>()
? Typy ogólne nie zawierają parametru type w czasie wykonywania. Parametr typu nie jest używany podczas tworzenia. Nie ma różnicy w kodzie generowanym przeznew ArrayList<SomeType>()
lubnew ArrayList<String>()
czynew ArrayList()
w ogóle.ArrayList<T>
działa ”private T[] myArray
. Gdzieś w kodzie musi mieć tablicę ogólnego typu T, więc jak?T[]
. Ma tablicę typu środowiska wykonawczegoObject[]
i albo 1) kod źródłowy zawiera zmiennąObject[]
(tak jest w najnowszym źródle Oracle Java); lub 2) kod źródłowy zawiera zmienną typuT[]
, która jest kłamstwem, ale nie powoduje problemów z powoduT
usunięcia z zakresu klasy.Zacytować:
(Myślę, że to Neal Gafter , ale nie jestem pewien)
Zobacz to w kontekście tutaj: http://forums.sun.com/thread.jspa?threadID=457033&forumID=316
źródło
new T()
. Każda tablica w Javie z założenia przechowuje w niej typ komponentu (tj.T.class
); dlatego potrzebujesz klasy T w czasie wykonywania, aby utworzyć taką tablicę.new Box<?>[n]
, co czasami może być wystarczające, chociaż nie pomogłoby to w twoim przykładzie.Box<String>[] bsa = new Box<String>[3];
czy coś się zmieniło w java-8 i więcej, zakładam?Nie dostarczając przyzwoitego rozwiązania, po prostu dostajesz coś gorszego IMHO.
Wspólne obejście jest następujące.
jest zastąpiony przez (zakładając, że T rozszerza Object, a nie inną klasę)
Wolę pierwszy przykład, jednak więcej typów akademickich wydaje się preferować drugi lub po prostu wolę o tym nie myśleć.
Większość przykładów, dlaczego nie możesz po prostu użyć Object [], odnosi się w równym stopniu do List lub Collection (które są obsługiwane), więc widzę je jako bardzo kiepskie argumenty.
Uwaga: jest to jeden z powodów, dla których sama biblioteka Kolekcje nie kompiluje się bez ostrzeżeń. Jeśli ten przypadek użycia nie może być obsługiwany bez ostrzeżeń, coś jest zasadniczo zepsute w ogólnym modelu IMHO.
źródło
String[]
(lub jeśli przechowujesz ją w polu publicznie dostępnym typuT[]
, a ktoś ją pobierze), wówczas otrzyma wyjątek ClassCastException.T[] ts = (T[]) new Object[n];
jest to zły pomysł: stackoverflow.com/questions/21577493/...T[] ts = new T[n];
to dobry przykład. Będę głosować, ponieważ jego odpowiedź może powodować problemy i zamieszanie w stosunku do innych deweloperów, a także jest nie na temat. Przestanę też komentować na ten temat.Tablice są kowariantne
Ta ostatnia linia byłaby dobrze skompilowana, ale gdybyśmy uruchomili ten kod, otrzymalibyśmy,
ArrayStoreException
ponieważ próbujemy umieścić podwójny w tablicy liczb całkowitych. Fakt, że uzyskujemy dostęp do tablicy za pomocą odwołania do liczby, nie ma tutaj znaczenia, ważne jest to, że tablica jest tablicą liczb całkowitych.Oznacza to, że możemy oszukać kompilator, ale nie możemy oszukać systemu typu wykonawczego. A to dlatego, że tablice są tym, co nazywamy typem możliwym do ponownego zdefiniowania. Oznacza to, że w czasie wykonywania Java wie, że ta tablica została faktycznie utworzona jako tablica liczb całkowitych, do których po prostu można uzyskać dostęp poprzez odwołanie typu
Number[]
.Tak więc, jak widzimy, jedna rzecz to faktyczny typ obiektu, a inna to rodzaj odwołania, którego używamy do uzyskania dostępu, prawda?
Problem z Generics Java
Problem z rodzajami rodzajowymi w Javie polega na tym, że kompilator porzuca kompilację informacji o typie dla parametrów typu; dlatego te informacje tego typu nie są dostępne w czasie wykonywania. Ten proces nazywa się kasowaniem typu . Istnieją dobre powody, aby implementować takie generyczne w Javie, ale to długa historia i ma ona związek z binarną zgodnością z istniejącym kodem.
Rozważmy teraz następujący niebezpieczny kod:
Jeśli kompilator Java nie powstrzyma nas przed zrobieniem tego, system typów wykonawczych też nas nie powstrzyma, ponieważ w czasie wykonywania nie ma możliwości ustalenia, że ta lista miała być tylko liczbą całkowitą. Środowisko wykonawcze Java pozwoliłoby nam umieścić na tej liście wszystko, co chcemy, gdy powinno ono zawierać tylko liczby całkowite, ponieważ kiedy zostało utworzone, zostało zadeklarowane jako lista liczb całkowitych. Dlatego kompilator odrzuca wiersz 4, ponieważ jest niebezpieczny i jeśli jest dozwolony, może złamać założenia systemu typów.
W związku z tym projektanci Java upewnili się, że nie możemy oszukać kompilatora. Jeśli nie możemy oszukać kompilatora (tak jak w przypadku tablic), nie możemy też oszukać systemu typu wykonawczego.
W związku z tym mówimy, że typy ogólne nie podlegają zwrotowi, ponieważ w czasie wykonywania nie możemy ustalić prawdziwej natury typu ogólnego.
Pominąłem niektóre części tych odpowiedzi, możesz przeczytać cały artykuł tutaj: https://dzone.com/articles/covariance-and-contravariance
źródło
Powodem tego jest to, że Java implementuje swoje Generics wyłącznie na poziomie kompilatora, a dla każdej klasy generowany jest tylko jeden plik klasy. Nazywa się to Kasowaniem Typu .
W czasie wykonywania skompilowana klasa musi obsłużyć wszystkie swoje zastosowania przy użyciu tego samego kodu bajtowego. Tak więc
new T[capacity]
nie miałbym absolutnie pojęcia, jakiego typu należy utworzyć.źródło
Odpowiedź została już udzielona, ale jeśli masz już wystąpienie T, możesz to zrobić:
Mam nadzieję, że mógłbym pomóc, Ferdi265
źródło
T[] ts = t.clone(); for (int i=0; i<ts.length; i++) ts[i] = null;
.T[] t
, to byłoby(T[]) Array.newInstance(t.getClass().getComponentType(), length);
. spędziłem trochę czasu, żeby to rozgryźćgetComponentType()
. Mam nadzieję, że to pomaga innym.t.clone()
nie wróciT[]
. Ponieważt
w tej odpowiedzi nie ma tablicy.Głównym powodem jest to, że tablice w Javie są kowariantne.
Jest to dobry przegląd tutaj .
źródło
String[]
doObject
i przechowywaćInteger
w nim. Musieli więc dodać sprawdzanie typu środowiska wykonawczego dla sklepów tablicowych (ArrayStoreException
), ponieważ problem nie mógł zostać wykryty w czasie kompilacji. (W przeciwnym razieInteger
rzeczywiście może utknąć w aString[]
, a przy próbie jego odzyskania wystąpiłby błąd, co byłoby okropne.) ...Object
powinienem byćObject[]
w moim pierwszym komentarzu.Podoba mi się odpowiedź udzielona pośrednio przez Gaftera . Jednak proponuję, aby było źle. Trochę zmieniłem kod Gaftera. Kompiluje się i działa przez chwilę, a następnie bombarduje zgodnie z przewidywaniami Gaftera
Dane wyjściowe to
Wydaje mi się, że możesz tworzyć ogólne typy tablic w Javie. Czy źle zrozumiałem pytanie?
źródło
Wiem, że jestem trochę spóźniony na przyjęcie tutaj, ale pomyślałem, że mogę pomóc przyszłym pracownikom Google, ponieważ żadna z tych odpowiedzi nie rozwiązała mojego problemu. Jednak odpowiedź Ferdi265 bardzo pomogła.
Próbuję utworzyć własną listę połączoną, więc dla mnie zadziałał następujący kod:
W metodzie toArray () leży sposób na stworzenie dla mnie tablicy typu ogólnego:
źródło
W moim przypadku po prostu chciałem mieć szereg stosów, coś takiego:
Ponieważ nie było to możliwe, zastosowałem następujące rozwiązanie:
Brzydkie, ale Java jest szczęśliwa.
Uwaga: jak wspomniano w BrainSlugs83 w komentarzu do pytania, jest całkowicie możliwe posiadanie tablic ogólnych w .NET
źródło
Z samouczka Oracle :
Dla mnie to brzmi bardzo słabo. Myślę, że każdy, kto ma wystarczającą wiedzę na temat generyków, byłby całkowicie w porządku, a nawet spodziewałby się, że wyjątek ArrayStoredException nie zostanie zgłoszony w takim przypadku.
źródło
Na pewno musi być dobry sposób na obejście tego (może przy użyciu odbicia), ponieważ wydaje mi się, że właśnie to
ArrayList.toArray(T[] a)
działa. Cytuję:Tak więc jednym z rozwiązań byłoby użycie tej funkcji, tj. Utworzenie
ArrayList
obiektu, który chcesz w tablicy, a następnie użycietoArray(T[] a)
do utworzenia rzeczywistej tablicy. Nie byłoby to szybkie, ale nie wspomniałeś o swoich wymaganiach.Czy ktoś wie, jak
toArray(T[] a)
jest wdrażany?źródło
Array.newInstance()
. Znajdziesz to wspomniane w wielu pytaniach, które pytają, jak utworzyć tablicę o typie nieznanym w czasie kompilacji. Ale OP specjalnie pytał, dlaczego nie można użyćnew T[]
składni, co jest innym pytaniemTo dlatego, że generycy zostali dodani do java po ich stworzeniu, więc jest to trochę niezgrabne, ponieważ oryginalni twórcy java myśleli, że podczas tworzenia tablicy typ będzie określony podczas jej tworzenia. Więc to nie działa z ogólnymi, więc musisz zrobić E [] array = (E []) new Object [15]; To się kompiluje, ale daje ostrzeżenie.
źródło
Jeśli nie możemy utworzyć instancji ogólnych tablic, dlaczego język ma ogólne typy tablic? Po co mieć typ bez obiektów?
Jedynym powodem, dla którego mogę wymyślić, jest varargs -
foo(T...)
. W przeciwnym razie mogliby całkowicie wyczyścić ogólne typy tablic. (Cóż, tak naprawdę nie musieli używać tablicy dla varargs, ponieważ varargs nie istniał przed 1.5 . To prawdopodobnie kolejny błąd).Więc jest to kłamstwo, to może instancję tablic rodzajowe, poprzez varargs!
Oczywiście problemy z tablicami ogólnymi są nadal realne, np
Możemy użyć tego przykładu, aby faktycznie wykazać niebezpieczeństwo związane z lekiem generycznym tablicą .
Z drugiej strony od dziesięciu lat używamy ogólnych varargów, a niebo jeszcze nie spada. Możemy więc argumentować, że problemy są przesadzone; to nic wielkiego. Jeśli dozwolone jest tworzenie jawnych tablic ogólnych, będziemy mieli błędy tu i tam; ale przyzwyczailiśmy się do problemów związanych z usuwaniem i możemy z tym żyć.
I możemy wskazać, aby
foo2
obalić twierdzenie, że specyfikacja chroni nas przed problemami, przed którymi twierdzą, że nas powstrzymują. Gdyby Sun miał więcej czasu i zasobów na 1,5 , uważam, że mogliby osiągnąć bardziej satysfakcjonującą rozdzielczość.źródło
Jak już wspomniano inni, możesz oczywiście tworzyć za pomocą kilku sztuczek .
Ale nie jest to zalecane.
Ponieważ typ wymazuje, a co ważniejsze,
covariance
tablica in, która po prostu zezwala na tablicę podtypów, może być przypisana do tablicy nadtypu, co zmusza do użycia rzutowania typu jawnego podczas próby odzyskania wartości powodując czas działania,ClassCastException
który jest jednym z głównych celów że generycy starają się wyeliminować: Silniejsze kontrole typów w czasie kompilacji .Bardziej bezpośredni przykład można znaleźć w Effective Java: Pozycja 25 .
kowariancja : tablica typu S [] jest podtypem T [], jeśli S jest podtypem T
źródło
Jeśli klasa używa jako sparametryzowanego typu, może zadeklarować tablicę typu T [], ale nie może bezpośrednio utworzyć takiej tablicy. Zamiast tego powszechnym podejściem jest tworzenie instancji tablicy typu Object [], a następnie wykonanie zwężenia rzutowania na typ T [], jak pokazano poniżej:
źródło
Spróbuj tego:
źródło