Czy w Javie można utworzyć instancję typu ogólnego? Myślę na podstawie tego, co widziałem, że odpowiedź brzmi no
(z powodu usunięcia typu ), ale byłbym zainteresowany, gdyby ktoś mógł zobaczyć coś, czego mi brakuje.
class SomeContainer<E>
{
E createContents()
{
return what???
}
}
EDYCJA: Okazuje się, że tokenów Super Type można użyć do rozwiązania mojego problemu, ale wymaga to dużo kodu opartego na refleksji, jak wskazały niektóre poniższe odpowiedzi.
Pozostawię to na chwilę otwarte, aby sprawdzić, czy ktoś wymyśli coś dramatycznie innego niż artykuł Artima Iana Robertsona .
Odpowiedzi:
Masz rację. Nie można tego zrobić
new E()
. Ale możesz to zmienić naTo ból. Ale to działa. Zawinięcie go w fabryczny wzór sprawia, że jest trochę bardziej tolerowany.
źródło
Class<?>
referencji za pomocą Guava i TypeToken, zobacz tę odpowiedź dla kodu i linków!Nie wiem, czy to pomaga, ale jeśli podklasujesz (w tym anonimowo) typ ogólny, informacje o typie są dostępne poprzez odbicie. na przykład,
Tak więc, gdy podklasujesz Foo, otrzymujesz instancję Bar np.
Ale to dużo pracy i działa tylko w przypadku podklas. Może się przydać.
źródło
Foo
nie jest abstrakcyjna. Ale dlaczego działa tylko na anonimowych podklasach Foo? Załóżmy, że robimyFoo
konkretny (pomijamyabstract
), dlaczego spowodujenew Foo<Bar>();
błąd, anew Foo<Bar>(){};
nie? (Wyjątek: „Nie można rzutować klasy na ParameterizedType”)<E>
inclass Foo<E>
nie jest związany z żadnym konkretnym typem. Pojawi się wyjątkową zachowanie, kiedyE
nie jest statycznie związane, na przykład:new Foo<Bar>()
,new Foo<T>() {...}
, lubclass Fizz <E> extends Foo<E>
. Pierwszy przypadek nie jest związany statycznie, jest kasowany podczas kompilacji. Drugi przypadek zastępuje inną zmienną typu (T) zamiast,E
ale nadal jest niezwiązany. I w ostatnim przypadku powinno być oczywiste, żeE
wciąż nie ma ograniczeń.class Fizz extends Foo<Bar>
- w tym przypadku użytkownicyFizz
otrzymują coś, co jestFoo<Bar>
i nie może być niczym innym jakFoo<Bar>
. W takim przypadku kompilator chętnie zakoduje te informacje w metadanych klasyFizz
i udostępni je jakoParameterizedType
kod odbicia. Kiedy tworzysz anonimową klasę wewnętrzną taknew Foo<Bar>() {...}
, jakby robiła to samo, tyle że zamiastFizz
kompilatora generuje „anonimową” nazwę klasy, której nie poznasz, dopóki klasa zewnętrzna nie zostanie skompilowana.Foo<Bar<Baz>>
. Będziesz tworzyć instancję,ParameterizedTypeImpl
której nie można jawnie utworzyć. Dlatego dobrym pomysłem jest sprawdzenie, czygetActualTypeArguments()[0]
zwraca aParameterizedType
. Jeśli tak, to chcesz uzyskać typ raw i zamiast tego utworzyć jego instancję.W Javie 8 możesz użyć
Supplier
funkcjonalnego interfejsu, aby osiągnąć to bardzo łatwo:Zbudowałbyś tę klasę w następujący sposób:
Składnia
String::new
w tym wierszu jest odwołaniem do konstruktora .Jeśli twój konstruktor przyjmuje argumenty, możesz zamiast tego użyć wyrażenia lambda:
źródło
SomeContainer stringContainer = new SomeContainer(String::new);
?Będziesz potrzebować jakiejś abstrakcyjnej fabryki, aby przekazać złotówkę:
źródło
Factory<>
to interfejs, więc nie ma ciała. Chodzi o to, że potrzebujesz warstwy pośrednich, aby przekazać złotówki metodom, które „znają” wymagany kod do budowy instancji. O wiele lepiej jest robić to z normalnym kodem niż z metalingwistycznymClass
lubConstructor
ponieważ odbicie powoduje cały świat bólu.SomeContainer<SomeElement> cont = new SomeContainer<>(SomeElement::new);
źródło
class GenericHome<T> extends Home<T>{}
Jeśli potrzebujesz nowej instancji argumentu typu w klasie ogólnej, poproś konstruktorów, aby zażądali jej klasy ...
Stosowanie:
Plusy:
Cons:
Foo<L>
dowód. Na początek ...newInstance()
wyrzuci wobbler, jeśli klasa argumentów typu nie ma domyślnego konstruktora. W każdym razie dotyczy to wszystkich znanych rozwiązań.źródło
Możesz to zrobić teraz i nie wymaga to dużego kodu refleksyjnego.
Oczywiście, jeśli trzeba wywołać konstruktora, który będzie wymagał refleksji, ale jest to bardzo dobrze udokumentowane, ta sztuczka nie jest!
Oto JavaDoc dla TypeToken .
źródło
Pomyśl o bardziej funkcjonalnym podejściu: zamiast tworzyć E z niczego (co jest wyraźnie zapachem kodu), przekaż funkcję, która wie, jak je stworzyć, tj.
źródło
Exception
użycieSupplier<E>
zamiast.Z samouczka Java - Ograniczenia dotyczące rodzajów ogólnych :
Nie można utworzyć wystąpień parametrów typu
Nie można utworzyć wystąpienia parametru typu. Na przykład poniższy kod powoduje błąd czasu kompilacji:
Aby obejść ten problem, możesz utworzyć obiekt parametru typu poprzez odbicie:
Możesz wywołać metodę dołączania w następujący sposób:
źródło
Oto opcja, którą wymyśliłem, może pomóc:
EDYCJA: Alternatywnie możesz użyć tego konstruktora (ale wymaga instancji E):
źródło
Jeśli nie chcesz wpisywać nazwy klasy dwa razy podczas tworzenia instancji, np .:
Możesz użyć metody fabrycznej:
Jak w:
źródło
Java niestety nie pozwala na to, co chcesz robić. Zobacz oficjalne obejście :
źródło
Kiedy pracujesz z E w czasie kompilacji, tak naprawdę nie obchodzi cię faktyczny typ ogólny „E” (albo używasz refleksji, albo pracujesz z podstawową klasą typu ogólnego), więc pozwól, aby podklasa dostarczyła instancję E.
źródło
Możesz użyć:
Musisz jednak podać dokładną nazwę klasy, w tym pakiety, np.
java.io.FileInputStream
. Użyłem tego do stworzenia parsera wyrażeń matematycznych.źródło
foo.getClass().getName()
. Skąd pochodzi ta instancja? Obecnie przekazuję jednego do konstruktora w projekcie, nad którym teraz pracuję.Mam nadzieję, że nie jest za późno na pomoc !!!
Java zapewnia bezpieczeństwo typu, tylko obiekt może utworzyć instancję.
W moim przypadku nie mogę przekazać parametrów do
createContents
metody. Moje rozwiązanie używa rozszerzeń zamiast wszystkich poniższych odpowiedzi.To mój przykładowy przypadek, w którym nie mogę przekazać parametrów.
Używając refleksji stwórz błąd wykonania, jeśli rozszerzysz swoją klasę ogólną o żaden typ obiektu. Aby rozszerzyć typ ogólny na obiekt, przekonwertuj ten błąd na błąd czasu kompilacji.
źródło
Użyj
TypeToken<T>
klasy:źródło
(T) new TypeToken<T>(getClass()){}.getRawType().newInstance();
Myślałem, że mogę to zrobić, ale byłem rozczarowany: to nie działa, ale myślę, że nadal warto się nim dzielić.
Może ktoś może poprawić:
Produkuje:
Linia 26 to ta z
[*]
.Jedynym realnym rozwiązaniem jest @JustinRudd
źródło
Ulepszenie odpowiedzi @ Noego.
Powód do zmiany
za] Jest bezpieczniejszy, jeśli użyjesz więcej niż 1 rodzaj ogólny w przypadku zmiany zamówienia.
b] Sygnatura typu ogólnego typu zmienia się od czasu do czasu, aby nie zaskoczyły Cię niewyjaśnione wyjątki w środowisku wykonawczym.
Solidny kod
Lub użyj jednej wkładki
Kod jednoliniowy
źródło
co możesz zrobić to -
Najpierw zadeklaruj zmienną tej klasy ogólnej
2. Następnie utwórz konstruktor i utwórz instancję tego obiektu
Następnie używaj go w dowolnym miejscu
przykład-
1
2)
3)
źródło
Jak powiedziałeś, tak naprawdę nie możesz tego zrobić z powodu wymazywania tekstu. Możesz to zrobić za pomocą refleksji, ale wymaga to dużo kodu i obsługi błędów.
źródło
Jeśli masz na myśli,
new E()
to jest niemożliwe. I dodałbym, że nie zawsze jest to poprawne - skąd wiesz, że E ma publiczny konstruktor bez argumentów? Ale zawsze możesz delegować tworzenie do innej klasy, która wie, jak utworzyć instancję - może to byćClass<E>
kod niestandardowy w ten sposóbźródło
źródło
SomeContainer
jest po prostuObject
. Dlategothis.getClass().getGenericSuperclass()
zwraca aClass
(klasa java.lang.Object), a nieParameterizedType
. Faktycznie zostało to już wskazane w odpowiedzi peer stackoverflow.com/questions/75175/ ....Możesz to zrobić za pomocą następującego fragmentu:
źródło
Istnieją różne biblioteki, które można rozwiązać
E
za pomocą technik podobnych do omawianych w artykule Robertsona. Oto implementacjacreateContents
użycia TypeTools do rozwiązania klasy surowej reprezentowanej przez E:Zakłada się, że getClass () przekształca się w podklasę SomeContainer i inaczej się nie powiedzie, ponieważ rzeczywista sparametryzowana wartość E zostanie skasowana w czasie wykonywania, jeśli nie zostanie przechwycona w podklasie.
źródło
Oto implementacja
createContents
wykorzystująca TypeTools do rozwiązania klasy surowej reprezentowanej przezE
:To podejście działa tylko wtedy, gdy
SomeContainer
jest podklasowane, więc rzeczywista wartośćE
jest przechwytywana w definicji typu:W przeciwnym razie wartość E jest usuwana w czasie wykonywania i nie można jej odzyskać.
źródło
Możesz to zrobić za pomocą programu ładującego klasy i nazwy klasy, ewentualnie niektórych parametrów.
źródło
Oto ulepszone rozwiązanie oparte na
ParameterizedType.getActualTypeArguments
, wspomnianym już przez @ noah, @Lars Bohl i kilku innych.Pierwsza niewielka poprawa wdrożenia. Fabryka nie powinna zwracać instancji, ale typ. Gdy tylko zwrócisz instancję
Class.newInstance()
, zmniejsz zakres użycia. Ponieważ tylko konstruktory bez argumentów mogą być wywoływane w ten sposób. Lepszym sposobem jest zwrócenie typu i umożliwienie klientowi wyboru, który konstruktor chce wywołać:Oto przykłady użycia. @Lars Bohl pokazał tylko znaczący sposób na uzyskanie potwierdzonej genetyki poprzez rozszerzenie. @ noah tylko poprzez utworzenie instancji za pomocą
{}
. Oto testy pokazujące oba przypadki:Uwaga: można zmusić klientów
TypeReference
zawsze używać{}
, gdy instancja jest tworzona przez co ta klasa abstrakcyjna:public abstract class TypeReference<T>
. Nie zrobiłem tego, tylko po to, aby pokazać wymazaną skrzynkę testową.źródło