Pojawiło się jako pytanie, które zadałem niedawno w wywiadzie, jako coś, co kandydat chciał, aby zostało dodane do języka Java. Powszechnie uważa się, że problem z tym, że Java nie ma zreifikowanych typów generycznych, ale gdy został popchnięty, kandydat nie mógł mi powiedzieć, jakie rzeczy mógłby osiągnąć, gdyby tam byli.
Oczywiście, ponieważ surowe typy są dozwolone w Javie (i niebezpieczne testy), możliwe jest obalenie typów generycznych i otrzymanie List<Integer>
tego (na przykład) faktycznie zawiera String
s. Oczywiście mogłoby to być niemożliwe, gdyby informacje o typie zostały zreifikowane; ale musi być coś więcej !
Czy ludzie mogliby publikować przykłady rzeczy, które naprawdę chcieliby zrobić , gdyby dostępne były reifikowane leki generyczne? To znaczy, oczywiście możesz uzyskać typ a List
w czasie wykonywania - ale co byś z tym zrobił?
public <T> void foo(List<T> l) {
if (l.getGenericType() == Integer.class) {
//yeah baby! err, what now?
EDYCJA : Szybka aktualizacja, ponieważ odpowiedzi wydają się być głównie zaniepokojone potrzebą przekazania a Class
jako parametru (na przykład EnumSet.noneOf(TimeUnit.class)
). Szukałem czegoś podobnego do tego, gdzie to po prostu nie jest możliwe . Na przykład:
List<?> l1 = api.gimmeAList();
List<?> l2 = api.gimmeAnotherList();
if (l1.getGenericType().isAssignableFrom(l2.getGenericType())) {
l1.addAll(l2); //why on earth would I be doing this anyway?
źródło
findClass()
musiałoby zignorować parametryzację, aledefineClass()
nie mógł). A jak wiemy, moce, które są, są najważniejsze, aby zachować zgodność wsteczną.Odpowiedzi:
Od kilku razy, kiedy natrafiłem na tę „potrzebę”, ostatecznie sprowadza się ona do tej konstrukcji:
To działa w C # przy założeniu, że
T
ma domyślnego konstruktora. Możesz nawet pobrać typ środowiska wykonawczegotypeof(T)
i konstruktory przezType.GetConstructor()
.Typowym rozwiązaniem w Javie byłoby przekazanie
Class<T>
argumentu as.(nie musi być koniecznie przekazywany jako argument konstruktora, ponieważ argument metody jest również w porządku, powyższe jest tylko przykładem, również
try-catch
jest pomijane dla zwięzłości)W przypadku wszystkich innych konstrukcji typu ogólnego rzeczywisty typ można łatwo rozwiązać przy niewielkiej pomocy refleksji. Poniższe pytania i odpowiedzi ilustrują przypadki użycia i możliwości:
źródło
T.class
lubT.getClass()
, aby mieć dostęp do wszystkich jej pól, konstruktorów i metod. Uniemożliwia również budowę.To, co najczęściej mnie gryzie, to niemożność skorzystania z wielokrotnych wysyłek w wielu typach ogólnych. Poniższe nie są możliwe i istnieje wiele przypadków, w których byłoby to najlepsze rozwiązanie:
źródło
public void my_method(List input) {}
. Jednak nigdy nie spotkałem się z tą potrzebą, po prostu dlatego, że nie mieliby tej samej nazwy. Jeśli mają to samo imię, zapytałbym, czypublic <T extends Object> void my_method(List<T> input) {}
nie jest to lepszy pomysł.myStringsMethod(List<String> input)
amyIntegersMethod(List<Integer> input)
nawet gdyby przeciążenie w takim przypadku było możliwe w Javie.<algorithm>
C ++.Przychodzi mi na myśl bezpieczeństwo typów . Downcasting do sparametryzowanego typu zawsze będzie niebezpieczny bez reified generics:
Ponadto abstrakcje wyciekałyby mniej - przynajmniej te, które mogą być zainteresowane informacjami w czasie wykonywania o ich parametrach typu. Dziś, jeśli potrzebujesz jakichkolwiek informacji o typie jednego z parametrów ogólnych, musisz je również przekazać
Class
. W ten sposób twój zewnętrzny interfejs zależy od twojej implementacji (czy używasz RTTI do swoich parametrów, czy nie).źródło
ParametrizedList
który kopiuje dane w kolekcji źródłowej, sprawdzając typy. To trochę jak,Collections.checkedList
ale na początek można je obsiać kolekcją.T.class.getAnnotation(MyAnnotation.class)
(gdzieT
jest typem ogólnym) bez zmiany interfejsu zewnętrznego.Byłbyś w stanie tworzyć ogólne tablice w swoim kodzie.
źródło
String[] strings = new String[1]; Object[] objects = strings; objects[0] = new Object();
Kompiluje się dobrze w obu językach. Działa niezbyt dobrze.To jest stare pytanie, jest mnóstwo odpowiedzi, ale myślę, że istniejące odpowiedzi są chybione.
„zreifikowany” oznacza po prostu prawdziwy i zwykle oznacza po prostu przeciwieństwo wymazania typu.
Duży problem związany z generykami Java:
void method(List<A> l)
imethod(List<B> l)
. Wynika to z wymazywania czcionek, ale jest bardzo drobiazgowe.źródło
deserialize(thingy, List<Integer>.class)
Serializacja byłaby prostsza w przypadku reifikacji. To, czego chcielibyśmy, to
Musimy zrobić
wygląda brzydko i działa dziwnie.
Są też przypadki, w których naprawdę pomocne byłoby powiedzenie czegoś takiego
Te rzeczy nie gryzą często, ale gryzą, gdy się zdarzają.
źródło
Moje narażenie na Java Geneircs jest dość ograniczone i poza punktami, o których wspomniały już inne odpowiedzi, istnieje scenariusz wyjaśniony w książce Java Generics and Collections , autorstwa Maurice'a Naftalina i Philipa Waldera, w której użyteczne są reifikowane typy generyczne.
Ponieważ typów nie można reifable, nie można mieć sparametryzowanych wyjątków.
Na przykład deklaracja poniższego formularza jest nieważna.
Dzieje się tak, ponieważ klauzula catch sprawdza, czy zgłoszony wyjątek pasuje do danego typu. To sprawdzenie jest takie samo jak sprawdzenie przeprowadzane przez test instancji, a ponieważ typu nie można ponowić, powyższa forma instrukcji jest nieprawidłowa.
Gdyby powyższy kod był prawidłowy, obsługa wyjątków w poniższy sposób byłaby możliwa:
W książce wspomniano również, że jeśli typy generyczne języka Java są zdefiniowane podobnie do sposobu definiowania szablonów C ++ (rozszerzenie), może to prowadzić do wydajniejszej implementacji, ponieważ oferuje to więcej możliwości optymalizacji. Ale nie oferuje więcej wyjaśnień, więc wszelkie wyjaśnienia (wskazówki) od znających się na rzeczy ludzi byłyby pomocne.
źródło
Tablice prawdopodobnie działałyby znacznie lepiej z rodzajami, gdyby były reifikowane.
źródło
List<String>
nie jestList<Object>
.Mam opakowanie, które przedstawia zestaw wyników jdbc jako iterator (oznacza to, że mogę dużo łatwiej testować jednostkowe operacje pochodzące z bazy danych dzięki iniekcji zależności).
Wygląda na to API
Iterator<T>
jest typem, który można skonstruować przy użyciu tylko ciągów znaków w konstruktorze. Następnie Iterator sprawdza ciągi zwracane przez zapytanie sql, a następnie próbuje dopasować je do konstruktora typu T.W obecnym sposobie implementacji typów generycznych muszę również przekazać klasę obiektów, które będę tworzyć z mojego zestawu wyników. Jeśli dobrze rozumiem, gdyby typy generyczne zostały zreifikowane, mógłbym po prostu wywołać funkcję T.getClass (), aby uzyskać jej konstruktory, a następnie nie musieć rzutować wyniku Class.newInstance (), co byłoby znacznie bardziej estetyczne.
Zasadniczo uważam, że ułatwia pisanie interfejsów API (w przeciwieństwie do zwykłego pisania aplikacji), ponieważ z obiektów można wywnioskować znacznie więcej, a tym samym mniej konfiguracji będzie potrzebnych ... Nie doceniałem implikacji adnotacji, dopóki nie widziałem ich użycie w rzeczach takich jak spring lub xstream zamiast ryz w config.
źródło
Jedną z fajnych rzeczy byłoby unikanie boksu dla typów pierwotnych (wartościowych). Jest to w pewnym stopniu związane z zarzutem dotyczącym tablicy, który podnieśli inni, aw przypadkach, gdy użycie pamięci jest ograniczone, może to faktycznie spowodować znaczącą różnicę.
Istnieje również kilka typów problemów podczas pisania frameworka, w których ważna jest możliwość odzwierciedlenia sparametryzowanego typu. Oczywiście można to obejść, przekazując obiekt klasy w czasie wykonywania, ale to przesłania API i nakłada dodatkowe obciążenie na użytkownika frameworka.
źródło
new T[]
gdzie T jest typu prymitywnego!Nie chodzi o to, że osiągniesz coś niezwykłego. Po prostu łatwiej będzie to zrozumieć. Wymazywanie typów wydaje się być trudnym czasem dla początkujących i ostatecznie wymaga zrozumienia sposobu działania kompilatora.
Uważam, że generyczne są po prostu dodatkami, które oszczędzają wiele zbędnych rzutów.
źródło
Coś, czego pominęły wszystkie odpowiedzi tutaj, a co jest dla mnie nieustannie bólem głowy, jest takie, że ponieważ typy są usuwane, nie można dwukrotnie dziedziczyć ogólnego interfejsu. Może to stanowić problem, gdy chcesz tworzyć precyzyjne interfejsy.
źródło
Oto jeden, który mnie dzisiaj złapał: bez reifikacji, jeśli napiszesz metodę, która akceptuje listę elementów ogólnych typu varargs ... wywołujący mogą MYŚLIĆ, że są bezpieczni dla typów, ale przypadkowo przekazują jakąkolwiek starą crud i wysadzają twoją metodę.
Wydaje się mało prawdopodobne, że tak się stanie? ... Jasne, dopóki ... nie użyjesz Class jako swojego typu danych. W tym momencie dzwoniący z radością wyśle Ci wiele obiektów Class, ale prosta literówka wyśle Ci obiekty Class, które nie są zgodne z T, i zdarzy się katastrofa.
(Uwaga: być może popełniłem tutaj błąd, ale przeglądając w Google „warargi generyczne”, powyższe wydaje się być właśnie tym, czego można się spodziewać. Rzeczą, która sprawia, że jest to praktyczny problem, jest, jak sądzę, korzystanie z Class - wydaje się, że rozmówcy być mniej ostrożnym :()
Na przykład używam paradygmatu, który używa obiektów klasy jako klucza w mapach (jest bardziej złożony niż prosta mapa - ale koncepcyjnie tak właśnie się dzieje).
np. to działa świetnie w Java Generics (trywialny przykład):
np. bez reifikacji w Java Generics, ten akceptuje KAŻDY obiekt "Class". A to tylko malutkie rozszerzenie poprzedniego kodu:
Powyższe metody muszą być zapisywane tysiące razy w indywidualnym projekcie - więc prawdopodobieństwo błędu ludzkiego staje się wysokie. Błędy w debugowaniu okazują się „niefajne”. Obecnie próbuję znaleźć alternatywę, ale nie mam nadziei.
źródło