Kilka razy widziałem na tej stronie posty, które potępiają implementację generycznych Java. Teraz mogę szczerze powiedzieć, że nie miałem żadnych problemów z ich używaniem. Jednak sam nie próbowałem stworzyć ogólnej klasy. Jakie masz problemy z ogólną obsługą Javy?
49
Odpowiedzi:
Ogólna implementacja Java wykorzystuje usuwanie typu . Oznacza to, że silnie typowane kolekcje ogólne są w rzeczywistości typami
Object
w czasie wykonywania. Ma to pewne względy dotyczące wydajności, ponieważ oznacza, że typy pierwotne muszą być umieszczane w ramkach po dodaniu do kolekcji ogólnej. Oczywiście korzyści z poprawności typu czasu kompilacji przewyższają ogólną głupotę usuwania typu i obsesyjne skupienie się na kompatybilności wstecznej.źródło
TypeLiteral
google-guice.googlecode.com/svn/trunk/javadoc/com/google/inject/...new ArrayList<String>.getClass() == new ArrayList<Integer>.getClass()
template<T> T add(T v1, T v2) { return v1->add(v2); }
a tam masz naprawdę ogólny sposób tworzenia funkcji, któraadd
jest dwiema rzeczami bez względu na to, co to są, muszą mieć metodę o nazwieadd()
z jednym parametrem.Add
przykładu, który podałem, nie ma mowy bez uprzedniej wiedzy, do których klas można zastosować, co jest wadą.Zauważ, że już udzielone odpowiedzi koncentrują się na połączeniu języka Java, JVM i biblioteki klas Java.
Jeśli chodzi o język Java, nie ma nic złego w ogólnych zasadach Javy. Jak opisano w języku C # w porównaniu z generycznymi programami Java, ogólne programy Java są w porządku na poziomie języka 1 .
Nieoptymalne jest to, że JVM nie obsługuje bezpośrednio generyków, co ma kilka poważnych konsekwencji:
Podejrzewam, że rozróżnienie, które robię, jest pedantyczne, ponieważ język Java jest powszechnie kompilowany z JVM jako celem, a Java Class Library jako podstawową biblioteką.
1 Z możliwym wyjątkiem symboli wieloznacznych, co, jak się uważa, powoduje, że wnioskowanie typu jest nierozstrzygalne w ogólnym przypadku. Jest to główna różnica między standardami C # i Java, która nie jest często wymieniana. Dzięki, Antymon.
źródło
8675309
) I stworzyć z niego typ unikalny dla tego numeru (np.Z8<Z6<Z7<Z5<Z3<Z0<Z9>>>>>>>
), Który miałby inne elementy niż jakikolwiek inny typ. W C ++ wszystkie typy, które można by wygenerować z dowolnego wejścia, musiałyby zostać wygenerowane w czasie kompilacji.Zwykłą krytyką jest brak reifikacji. Innymi słowy, obiekty w czasie wykonywania nie zawierają informacji o ich ogólnych argumentach (chociaż informacje te są nadal obecne w polach, metodzie, konstruktorach oraz rozszerzonej klasie i interfejsach). Oznacza to, że możesz rzucić, powiedzmy,
ArrayList<String>
naList<File>
. Kompilator wyświetli ostrzeżenia, ale ostrzeże również, jeśli weźmieszArrayList<String>
przypisanie doObject
odwołania, a następnie prześlesz jeList<String>
. Zaletą jest to, że przy generyce prawdopodobnie nie powinieneś robić rzutowania, wydajność jest lepsza bez niepotrzebnych danych i, oczywiście, wstecznej kompatybilności.Niektóre osoby narzekają, że nie można przeciążać na podstawie ogólnych argumentów (
void fn(Set<String>)
ivoid fn(Set<File>)
). Zamiast tego musisz użyć lepszych nazw metod. Zauważ, że to przeciążenie nie wymagałoby ponownej weryfikacji, ponieważ przeciążenie jest statycznym problemem podczas kompilacji.Typy pierwotne nie działają z ogólnymi.
Symbole wieloznaczne i granice są dość skomplikowane. Są bardzo przydatne. Jeśli Java wolałaby interfejsy niezmienności i polecenia „nie pytaj”, bardziej odpowiednie byłyby ogólne informacje po stronie deklaracji niż po stronie użycia.
źródło
Object cs = new char[] { 'H', 'i' }; System.out.println(cs);
wydrukowane bzdury. Zmień typcs
na,char[]
a dostanieszHi
.Generycy Java są do bani, ponieważ nie można wykonać następujących czynności:
Nie musiałbyś rozbijać wszystkich klas w powyższym kodzie, aby działał tak, jak powinien, gdyby generics były rzeczywiście ogólnymi parametrami klasy.
źródło
Parser
, będę musiał jeszcze bardziej skomplikować kod fabryczny. Podczas gdy „ogólne” naprawdę oznaczałoby ogólne, wówczas nie byłoby potrzeby fabryk i wszystkich innych bzdur, które noszą nazwę wzorców projektowych. To jeden z wielu powodów, dla których wolę pracować w dynamicznych językach.ostrzeżenia kompilatora o brakujących ogólnych parametrach, które nie służą żadnemu celowi, sprawiają, że język jest bezcelowo gadatliwy, np
public String getName(Class<?> klazz){ return klazz.getName();}
Generyczne nie działają dobrze z tablicami
Informacje o utraconym typie sprawiają, że odbicie jest bałaganem odlewania i taśmy izolacyjnej.
źródło
HashMap
zamiastHashMap<String, String>
.Sądzę, że inne odpowiedzi mówiły to do pewnego stopnia, ale niezbyt wyraźnie. Jednym z problemów z lekami generycznymi jest utrata typów ogólnych podczas procesu refleksji. Na przykład:
Niestety zwrócone typy nie mogą powiedzieć, że ogólne typy arr to String. To subtelna różnica, ale ważna. Ponieważ arr jest tworzony w czasie wykonywania, typy ogólne są usuwane w czasie wykonywania, więc nie możesz tego rozgryźć. Jak niektórzy twierdzą,
ArrayList<Integer>
wygląda tak samo, jakArrayList<String>
z punktu widzenia refleksji.Może to nie mieć znaczenia dla użytkownika Generics, ale powiedzmy, że chcieliśmy stworzyć fantazyjne ramy, które wykorzystałyby refleksję, aby wymyślić fantazyjne rzeczy na temat tego, jak użytkownik zadeklarował konkretne typy ogólne instancji.
Powiedzmy, że chcieliśmy, aby fabryka ogólna utworzyła instancję,
MySpecialObject
ponieważ jest to konkretny typ ogólny, który zadeklarowaliśmy dla tego wystąpienia. Cóż, klasa Factory nie może się przesłuchać w celu ustalenia konkretnego typu zadeklarowanego dla tego wystąpienia, ponieważ Java je usunęła.W generics .Net możesz to zrobić, ponieważ w czasie wykonywania obiekt wie, że jest to rodzaj ogólny, ponieważ kompilator zastosował go do pliku binarnego. Po wymazaniu Java nie może tego zrobić.
źródło
Mógłbym powiedzieć kilka dobrych rzeczy na temat leków generycznych, ale to nie było pytanie. Mógłbym narzekać, że nie są one dostępne w czasie wykonywania i nie działają z tablicami, ale zostało to wspomniane.
Duża irytacja psychologiczna: czasami wpadam w sytuacje, w których nie można zmusić leków generycznych do działania. (Tablice są najprostszym przykładem.) I nie mogę zrozumieć, czy leki generyczne nie są w stanie wykonać tej pracy, czy też jestem po prostu głupi. Nienawidzę tego. Coś w rodzaju leków generycznych powinno zawsze działać. Za każdym razem, gdy nie mogę robić tego, co chcę, używając języka Java, wiem, że problem to ja i wiem, że jeśli będę dalej naciskać, w końcu tam dotrę. W przypadku leków generycznych, jeśli stanę się zbyt wytrwały, mogę zmarnować dużo czasu.
Ale prawdziwym problemem jest to, że leki generyczne dodają zbyt dużej złożoności dla zbyt małego zysku. W najprostszych przypadkach może mnie to powstrzymać od dodania jabłka do listy zawierającej samochody. W porządku. Ale bez generyków ten błąd spowodowałby, że ClassCastException byłby bardzo szybki w czasie wykonywania, bez marnowania czasu. Jeśli dodam samochód z fotelikiem dziecięcym z dzieckiem, czy potrzebuję ostrzeżenia podczas kompilacji, że lista dotyczy tylko samochodów z fotelikami dziecięcymi, które zawierają szympansy dziecięce? Lista prostych instancji Object zaczyna wyglądać na dobry pomysł.
Kod ogólny może zawierać wiele słów i znaków, a także zajmować dużo miejsca i czytać. Mogę spędzić dużo czasu, żeby cały ten dodatkowy kod działał poprawnie. Kiedy tak się dzieje, czuję się niesamowicie sprytny. Zmarnowałem też kilka godzin lub więcej własnego czasu i muszę się zastanawiać, czy ktokolwiek kiedykolwiek będzie w stanie znaleźć kod. Chciałbym móc przekazać to na utrzymanie osobom, które są mniej inteligentne ode mnie lub mają mniej czasu do stracenia.
Z drugiej strony (trochę się tam skończyłem i czuję potrzebę zapewnienia równowagi), miło jest, gdy używa się prostych kolekcji i map do dodawania, wstawiania i sprawdzania podczas pisania i zwykle nie dodaje dużo złożoność kodu ( jeśli ktoś inny napisze kolekcję lub mapę) . I Java jest w tym lepsza niż C #. Wygląda na to, że żadna z kolekcji C #, której chcę używać, nie obsługuje generycznych. (Przyznaję, że mam dziwne upodobania w kolekcjach).
źródło
Fine. But without generics this error would throw a ClassCastException really quick at run time with little time wasted.
To naprawdę zależy od tego, ile czasu występuje między uruchomieniem programu a dotknięciem linii danego kodu. Jeśli użytkownik zgłosi błąd i odtworzenie go zajmie kilka minut (lub, w najgorszym przypadku, godziny lub nawet dni), weryfikacja czasu kompilacji zacznie wyglądać coraz lepiej ...