Ze względu na implementację generycznych Java, nie możesz mieć takiego kodu:
public class GenSet<E> {
private E a[];
public GenSet() {
a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation
}
}
Jak mogę to zaimplementować, zachowując bezpieczeństwo typu?
Na forach Java zobaczyłem takie rozwiązanie:
import java.lang.reflect.Array;
class Stack<T> {
public Stack(Class<T> clazz, int capacity) {
array = (T[])Array.newInstance(clazz, capacity);
}
private final T[] array;
}
Ale tak naprawdę nie rozumiem, co się dzieje.
java
arrays
generics
reflection
instantiation
tatsuhirosatou
źródło
źródło
Odpowiedzi:
W zamian muszę zadać pytanie: czy Twoje
GenSet
„zaznaczone” czy „niezaznaczone”? Co to znaczy?Sprawdzone : mocne pisanie .
GenSet
wie wyraźnie, jaki typ obiektów zawiera (tzn. jego konstruktor został jawnie wywołany zClass<E>
argumentem, a metody zgłoszą wyjątek, gdy zostaną przekazane argumenty, które nie są typuE
. ZobaczCollections.checkedCollection
.-> w takim przypadku powinieneś napisać:
Niezaznaczone : słabe pisanie . W rzeczywistości żaden sprawdzanie typu nie jest wykonywane na żadnym obiekcie przekazywanym jako argument.
-> w takim przypadku powinieneś napisać
Zauważ, że typem komponentu tablicy powinno być usunięcie parametru type:
Wszystko to wynika ze znanej i celowej słabości generyków w Javie: zostało zaimplementowane przy użyciu skasowania, więc klasy „ogólne” nie wiedzą, z jakim argumentem typu zostały utworzone w czasie wykonywania, a zatem nie mogą dostarczyć typu bezpieczeństwo, chyba że wdrożony zostanie jakiś jawny mechanizm (kontrola typu).
źródło
Object[] EMPTY_ELEMENTDATA = {}
do przechowywania. Czy mogę użyć tego mechanizmu do zmiany rozmiaru bez znajomości typu za pomocą generycznych?public void <T> T[] newArray(Class<T> type, int length) { ... }
Możesz to zrobić:
Jest to jeden z sugerowanych sposobów implementacji ogólnej kolekcji w Effective Java; Pozycja 26 . Bez błędów typu, nie trzeba wielokrotnie rzutować tablicy. Jednak to wyzwala ostrzeżenie, ponieważ jest potencjalnie niebezpieczny i powinien być stosowany z ostrożnością. Jak wyszczególniono w komentarzach,
Object[]
teraz jest to maskarada naszegoE[]
typu i może powodować nieoczekiwane błędy,ClassCastException
jeśli zostanie użyta niezgodnie z przeznaczeniem.Zasadniczo takie zachowanie jest bezpieczne, o ile tablica rzutowania jest używana wewnętrznie (np. Do tworzenia kopii zapasowej struktury danych) i nie jest zwracana ani narażana na kod klienta. Jeśli musisz zwrócić tablicę typu ogólnego do innego kodu, wspomniana
Array
klasa odbicia jest właściwą drogą.Warto wspomnieć, że tam, gdzie to możliwe, będziesz mieć dużo więcej czasu na pracę z
List
s niż z tablicami, jeśli używasz ogólnych. Z pewnością czasami nie masz wyboru, ale korzystanie ze struktury kolekcji jest znacznie bardziej niezawodne.źródło
String[] s=b;
w powyższejtest()
metodzie. To dlatego, że tablica E nie jest tak naprawdę, to Object []. Ma to znaczenie, jeśli chcesz, np. AList<String>[]
- nie możesz użyćObject[]
do tego, musisz miećList[]
konkretny. Dlatego musisz użyć odzwierciedlonego tworzenia tablicy klasy <?>.public E[] toArray() { return (E[])internalArray.clone(); }
gdyinternalArray
jest wpisany jakoE[]
, a zatem w rzeczywistości jestObject[]
. Nie udaje się to w czasie wykonywania z wyjątkiem rzutowania typu, ponieważObject[]
nie można przypisać tablicy do tablicy dowolnego typuE
.E[] b = (E[])new Object[1];
wyraźnie widać, że jedynym odwołaniem do utworzonej tablicy jestb
i że typemb
jestE[]
. Dlatego nie ma niebezpieczeństwa przypadkowego dostępu do tej samej tablicy przez inną zmienną innego typu. Jeśli tak,Object[] a = new Object[1]; E[]b = (E[])a;
to musiałbyś być paranoikiem na temat tego, jak używasza
.Oto jak używać ogólnych, aby uzyskać tablicę dokładnie tego, czego szukasz, przy jednoczesnym zachowaniu bezpieczeństwa typu (w przeciwieństwie do innych odpowiedzi, które albo zwrócą
Object
tablicę, albo spowodują pojawienie się ostrzeżeń w czasie kompilacji):To się kompiluje bez ostrzeżeń i jak widać w
main
, dla dowolnego typu deklarowanego jako instancjęGenSet
, można przypisaća
do tablicy tego typu i można przypisać elementa
do zmiennej tego typu, co oznacza, że tablica a wartości w tablicy są poprawnego typu.Działa przy użyciu literałów klasowych jako tokenów typu wykonawczego, jak omówiono w samouczkach Java . Literały klas są traktowane przez kompilator jako instancje
java.lang.Class
. Aby użyć jednego, po prostu podążaj za nazwą klasy za pomocą.class
.String.class
Działa więc jakoClass
obiekt reprezentujący klasęString
. Działa to również w przypadku interfejsów, wyliczeń, tablic dowolnego wymiaru (np.String[].class
), Prymitywów (np.int.class
) I słowa kluczowegovoid
(tjvoid.class
.).Class
sam jest ogólny (zadeklarowany jakoClass<T>
, gdzieT
oznacza typ reprezentowany przezClass
obiekt), co oznacza, że typemString.class
jestClass<String>
.Tak więc, za każdym razem, gdy wywołujesz konstruktor
GenSet
, przekazujesz literał klasy dla pierwszego argumentu reprezentującego tablicęGenSet
zadeklarowanego typu instancji (np.String[].class
DlaGenSet<String>
). Zauważ, że nie będziesz w stanie uzyskać tablicy prymitywów, ponieważ prymitywów nie można używać do zmiennych typu.Wywołanie metody wewnątrz konstruktora
cast
zwraca przekazanyObject
argument rzutowany do klasy reprezentowanej przezClass
obiekt, na którym wywołano metodę. Wywołanie metody statycznejnewInstance
wjava.lang.reflect.Array
zwraca jakoObject
tablicę typu reprezentowanego przezClass
obiekt przekazany jako pierwszy argument i o długości określonej przezint
przekazany jako drugi argument. Wywołanie metodygetComponentType
zwracającejClass
Przedmiotem reprezentującym typ komponentu tablicy reprezentowane przezClass
obiekt na którym sposób został nazwany (na przykładString.class
przezString[].class
,null
jeśliClass
obiekt nie stanowią macierz).To ostatnie zdanie nie jest do końca dokładne. Wywołanie
String[].class.getComponentType()
zwracaClass
obiekt reprezentujący klasęString
, ale jego typClass<?>
nieClass<String>
jest, dlatego nie można zrobić czegoś takiego jak poniżej.To samo dotyczy każdej metody,
Class
która zwracaClass
obiekt.W odniesieniu do komentarza Joachima Sauera do tej odpowiedzi (nie mam wystarczającej reputacji, aby skomentować ją osobiście), przykład użycia cast do
T[]
spowoduje ostrzeżenie, ponieważ kompilator nie może zagwarantować bezpieczeństwa typu w tym przypadku.Edytuj w odniesieniu do komentarzy Ingo:
źródło
To jedyna odpowiedź bezpieczna dla typu
źródło
Arrays#copyOf()
jest niezależny od długości tablicy podanej jako pierwszy argument. To sprytne, choć niesie za sobą koszty połączeń doMath#min()
iSystem#arrayCopy()
żadne z nich nie jest absolutnie konieczne do wykonania tej pracy. docs.oracle.com/javase/7/docs/api/java/util/…E
jest zmienną typu. Varargs tworzy tablicę wymazywaniaE
kiedyE
jest zmienną typu, dzięki czemu niewiele się różni(E[])new Object[n]
. Zobacz http://ideone.com/T8xF91 . W żadnym wypadku nie jest bardziej bezpieczny niż jakakolwiek inna odpowiedź.new E[]
. Problem, który pokazałeś w swoim przykładzie, to ogólny problem usuwania, nie unikalny dla tego pytania i odpowiedzi.Aby rozszerzyć na więcej wymiarów, po prostu dodaj
[]
parametry i parametry wymiaru donewInstance()
(T
jest parametrem typu,cls
aClass<T>
,d1
przez,d5
są liczbami całkowitymi):Zobacz
Array.newInstance()
szczegóły.źródło
W Javie 8 możemy wykonać rodzaj ogólnego tworzenia tablic za pomocą lambda lub odwołania do metody. Jest to podobne do podejścia refleksyjnego (które przechodzi a
Class
), ale tutaj nie używamy refleksji.Na przykład jest to używane przez
<A> A[] Stream.toArray(IntFunction<A[]>)
.Można to również zrobić przed wersją Java 8 przy użyciu anonimowych klas, ale jest to bardziej kłopotliwe.
źródło
ArraySupplier
, możesz zadeklarować konstruktor jakoGenSet(Supplier<E[]> supplier) { ...
i wywołać go tym samym wierszem, co masz.IntFunction<E[]>
, ale tak, to prawda.Jest to omówione w rozdziale 5 (Generics) Effective Java, 2nd Edition , pozycja 25 ... Preferuj listy od tablic
Twój kod będzie działał, chociaż będzie generował niesprawdzone ostrzeżenie (które możesz pominąć za pomocą następującej adnotacji:
Jednak prawdopodobnie lepiej byłoby użyć listy zamiast tablicy.
Ciekawa dyskusja na temat tego błędu / funkcji na stronie projektu OpenJDK .
źródło
Nie trzeba przekazywać argumentu Konstruktor do klasy. Spróbuj tego.
i
wynik:
źródło
Generyczne programy Java działają poprzez sprawdzanie typów w czasie kompilacji i wstawianie odpowiednich rzutowań, ale kasowanie typów w skompilowanych plikach. Dzięki temu biblioteki ogólne mogą być używane przez kod, który nie rozumie generycznych (co było świadomą decyzją projektową), ale co oznacza, że normalnie nie można dowiedzieć się, jaki typ jest w czasie wykonywania.
Konstruktor publiczny
Stack(Class<T> clazz,int capacity)
wymaga przekazania obiektu klasy w czasie wykonywania, co oznacza, że informacje o klasie są dostępne w czasie wykonywania do potrzebującego kodu. IClass<T>
środki tworzą, że kompilator będzie sprawdzić, czy obiekt klasy przechodzą właśnie obiekt Class dla typu T. nie podklasą T, nie nadklasą T, ale właśnie T.Oznacza to, że możesz utworzyć w swoim konstruktorze obiekt tablicowy odpowiedniego typu, co oznacza, że typ obiektów przechowywanych w Twojej kolekcji będzie sprawdzany pod kątem typów w momencie dodawania do kolekcji.
źródło
Cześć, chociaż wątek jest martwy, chciałbym zwrócić uwagę na to:
Generics służy do sprawdzania typu w czasie kompilacji:
Nie martw się ostrzeżeniami rzutowania podczas pisania klasy ogólnej. Martw się, gdy go używasz.
źródło
Co z tym rozwiązaniem?
Działa i wygląda na zbyt prosty, aby był prawdziwy. Czy jest jakaś wada?
źródło
T[]
, to nie możesz programowo zbudowaćT[] elems
polecenia przejścia do funkcji. A gdybyś mógł, nie potrzebujesz tej funkcji.Zobacz także ten kod:
Konwertuje listę dowolnego rodzaju obiektu na tablicę tego samego typu.
źródło
List
zawiera więcej niż jeden typ obiektu, np .toArray(Arrays.asList("abc", new Object()))
WyrzuciArrayStoreException
.for
pętli i innych, których użyłem,Arrays.fill(res, obj);
ponieważ chciałem mieć taką samą wartość dla każdego indeksu.Znalazłem szybki i łatwy sposób, który mi odpowiada. Zauważ, że używałem tego tylko w Javie JDK 8. Nie wiem, czy będzie działać z poprzednimi wersjami.
Chociaż nie możemy utworzyć instancji tablicy ogólnej parametru określonego typu, możemy przekazać już utworzoną tablicę do konstruktora klasy ogólnej.
Teraz w zasadzie możemy utworzyć tablicę w następujący sposób:
Dla większej elastyczności macierzy możesz użyć połączonej listy, np. ArrayList i inne metody znalezione w klasie Java.util.ArrayList.
źródło
W przykładzie wykorzystano odbicie Java do utworzenia tablicy. Nie jest to zalecane, ponieważ nie jest to bezpieczne. Zamiast tego powinieneś po prostu użyć wewnętrznej listy i całkowicie unikać tablicy.
źródło
Przekazywanie listy wartości ...
źródło
Zrobiłem ten fragment kodu, aby refleksyjnie utworzyć instancję klasy, która jest przekazywana do prostego narzędzia do automatycznego testowania.
Uwaga na ten segment:
do inicjowania tablicy gdzie Array.newInstance (klasa tablicy, rozmiar tablicy) . Klasa może być zarówno pierwotna (int. Klasa), jak i obiektowa (Integer.class).
BeanUtils jest częścią wiosny.
źródło
W rzeczywistości łatwiejszym sposobem jest utworzenie tablicy obiektów i rzutowanie jej na pożądany typ, jak w poniższym przykładzie:
gdzie
SIZE
jest stałą iT
jest identyfikatorem typuźródło
Przymusowa obsada sugerowana przez innych ludzi nie działała dla mnie, rzucając wyjątek nielegalnego castingu.
Jednak ta domyślna obsada działała dobrze:
gdzie pozycja jest zdefiniowaną przeze mnie klasą zawierającą element członkowski:
W ten sposób otrzymujesz tablicę typu K (jeśli element ma tylko wartość) lub dowolny typ ogólny, który chcesz zdefiniować w klasie Element.
źródło
Nikt inny nie odpowiedział na pytanie, co się dzieje w opublikowanym przykładzie.
Jak powiedzieli inni, generycy są „wymazywani” podczas kompilacji. Tak więc w czasie wykonywania instancja rodzaju ogólnego nie wie, jaki jest jego typ komponentu. Przyczyna tego jest historyczna, Sun chciał dodać ogólne, nie psując istniejącego interfejsu (zarówno źródłowego, jak i binarnego).
Tablice z drugiej strony zrób poznać ich typ komponentu przy starcie.
Ten przykład rozwiązuje ten problem, ponieważ kod wywołujący konstruktor (który zna typ) przekazuje parametr informujący klasę o wymaganym typie.
Więc aplikacja zbuduje klasę z czymś podobnym
a konstruktor wie teraz (w czasie wykonywania), jaki jest typ komponentu i może wykorzystać te informacje do skonstruowania tablicy za pomocą interfejsu API odbicia.
Wreszcie mamy rzutowany typ, ponieważ kompilator nie ma możliwości dowiedzenia się, że zwrócona tablica
Array#newInstance()
jest poprawnym typem (chociaż wiemy).Ten styl jest nieco brzydki, ale czasem może być najgorszym rozwiązaniem do tworzenia typów ogólnych, które z jakiegokolwiek powodu muszą znać swój typ komponentu w czasie wykonywania (tworzenie tablic lub tworzenie instancji typu komponentu itp.).
źródło
Znalazłem sposób obejścia tego problemu.
Wiersz poniżej generuje ogólny błąd tworzenia tablicy
Jeśli jednak opakowuję
List<Person>
w osobną klasę, to działa.Możesz wystawiać ludzi z klasy PersonList przez getter. W poniższej linii otrzymasz tablicę, która ma
List<Person>
w każdym elemencie. Innymi słowy tablicaList<Person>
.Potrzebowałem czegoś takiego w jakimś kodzie, nad którym pracowałem i właśnie to zrobiłem, aby to zadziałało. Jak dotąd żadnych problemów.
źródło
Możesz stworzyć tablicę Object i rzucić ją na E wszędzie. Tak, nie jest to bardzo czysty sposób, ale powinien przynajmniej działać.
źródło
Spróbuj tego.
źródło
Element
pochodzi twoja klasa?Łatwym, aczkolwiek niechlujnym obejściem tego problemu byłoby zagnieżdżenie drugiej klasy „holder” wewnątrz głównej klasy i użycie jej do przechowywania danych.
źródło
new Holder<Thing>[10]
to ogólne tworzenie tablic.Może nie ma to związku z tym pytaniem, ale podczas otrzymywania
generic array creation
błędu „ ” występowałemOdnajduję następujące prace (i pracowałem dla mnie) z
@SuppressWarnings({"unchecked"})
:źródło
Zastanawiam się, czy ten kod stworzyłby skuteczną ogólną tablicę?
Edycja: Być może alternatywnym sposobem utworzenia takiej tablicy, jeśli wymagany rozmiar byłby znany i mały, byłoby po prostu wprowadzenie wymaganej liczby „zerowych” do polecenia zeroArray?
Chociaż oczywiście nie jest to tak wszechstronne, jak użycie kodu createArray.
źródło
T
kiedyT
jest zmienną typu, tzn.zeroArray
Zwraca anObject[]
. Zobacz http://ideone.com/T8xF91 .Możesz użyć obsady:
źródło
a
na zewnątrz klasy!Znalazłem dość unikalne rozwiązanie, które pomija niemożność zainicjowania ogólnej tablicy. Musisz stworzyć klasę, która przyjmuje zmienną ogólną T w taki sposób:
a następnie w klasie macierzy po prostu zacznij tak:
uruchomienie a
new Generic Invoker[]
spowoduje problem z niezaznaczonym, ale tak naprawdę nie powinno być żadnych problemów.Aby uzyskać z tablicy, należy wywołać tablicę [i] .variable w następujący sposób:
Resztę, na przykład zmianę rozmiaru tablicy, można wykonać za pomocą Arrays.copyOf () w następujący sposób:
A funkcję dodawania można dodać w następujący sposób:
źródło
T
, a nie tablicy jakiegoś sparametryzowanego typu.Według vnportnoy składnia
tworzy tablicę pustych referencji, które należy wypełnić jako
który jest bezpieczny dla typu.
źródło
źródło
Ogólne tworzenie tablic jest niedozwolone w Javie, ale można to zrobić w podobny sposób
źródło