Czasami słyszałem, że w przypadku leków generycznych Java nie radziła sobie dobrze. (najbliższa referencja, tutaj )
Przepraszam za mój brak doświadczenia, ale co by je polepszyło?
Czasami słyszałem, że w przypadku leków generycznych Java nie radziła sobie dobrze. (najbliższa referencja, tutaj )
Przepraszam za mój brak doświadczenia, ale co by je polepszyło?
Odpowiedzi:
Zły:
List<byte>
naprawdę jest wspierany przez byte[]
na przykład i nie jest wymagany boks)Dobry:
Największym problemem jest to, że generyczne programy Java są dostępne tylko w czasie kompilacji i można to zmienić w czasie wykonywania. C # jest chwalony, ponieważ wykonuje więcej sprawdzania w czasie wykonywania. W tym poście znajduje się naprawdę dobra dyskusja , która zawiera linki do innych dyskusji.
Class
obiekty.
Głównym problemem jest to, że Java w rzeczywistości nie ma generycznych w czasie wykonywania. Jest to funkcja czasu kompilacji.
Kiedy tworzysz klasę ogólną w Javie, używają metody o nazwie „Type Erasure”, aby faktycznie usunąć wszystkie typy ogólne z klasy i zasadniczo zastąpić je Object. Wersja typów generycznych o długości mili jest taka, że kompilator po prostu wstawia rzutowania do określonego typu ogólnego, gdy pojawia się on w treści metody.
Ma to wiele wad. Jednym z największych, IMHO, jest to, że nie można użyć odbicia do sprawdzenia typu ogólnego. Typy nie są w rzeczywistości generyczne w kodzie bajtowym i dlatego nie mogą być sprawdzane jako ogólne.
Świetny przegląd różnic tutaj: http://www.jprl.com/Blog/archive/development/2007/Aug-31.html
(1) prowadzi do bardzo dziwnego zachowania. Najlepszy przykład, jaki przychodzi mi do głowy, to. Założyć:
public class MyClass<T> {
T getStuff() { ... }
List<String> getOtherStuff() { ... }
}
następnie zadeklaruj dwie zmienne:
MyClass<T> m1 = ...
MyClass m2 = ...
Teraz zadzwoń getOtherStuff()
:
List<String> list1 = m1.getOtherStuff();
List<String> list2 = m2.getOtherStuff();
Drugi ma swój argument typu ogólnego usunięty przez kompilator, ponieważ jest to typ surowy (co oznacza, że typ sparametryzowany nie jest dostarczany), mimo że nie ma nic wspólnego z typem sparametryzowanym.
Wspomnę też o mojej ulubionej deklaracji z JDK:
public class Enum<T extends Enum<T>>
Oprócz symboli wieloznacznych (które są mieszane) po prostu myślę, że generyczne .Net są lepsze.
public class Redundancy<R extends Redundancy<R>>
;)
The expression of type List needs unchecked conversion to conform to List<String>
Enum<T extends Enum<T>>
na początku może wydawać się dziwne / zbędne, ale w rzeczywistości jest całkiem interesujące, przynajmniej w ramach ograniczeń Javy / jej rodzajów. Wyliczenia mają values()
metodę statyczną, która daje tablicę ich elementów wpisanych jako wyliczenie, a nie Enum
, a ten typ jest określany przez parametr ogólny, co oznacza, że chcesz Enum<T>
. Oczywiście to wpisywanie ma sens tylko w kontekście wyliczonego typu, a wszystkie wyliczenia są podklasami Enum
, więc chcesz Enum<T extends Enum>
. Jednak Java nie lubi mieszać typów surowych z rodzajami, co Enum<T extends Enum<T>>
zapewnia spójność.
Wyrzucę naprawdę kontrowersyjną opinię. Generics komplikują język i komplikują kod. Na przykład, powiedzmy, że mam mapę, która mapuje ciąg na listę ciągów. W dawnych czasach mógłbym to po prostu określić jako
Map someMap;
Teraz muszę to zadeklarować jako
Map<String, List<String>> someMap;
I za każdym razem, gdy przekazuję to do jakiejś metody, muszę od nowa powtarzać tę wielką, długą deklarację. Moim zdaniem całe to dodatkowe wpisywanie odwraca uwagę programisty i wyprowadza go ze „strefy”. Ponadto, gdy kod jest wypełniony dużą ilością cruft, czasami trudno jest do niego wrócić później i szybko przejrzeć wszystkie okrucieństwa, aby znaleźć ważną logikę.
Java ma już złą reputację jako jeden z najbardziej rozwlekłych języków w powszechnym użyciu, a rodzaje generyczne tylko dodają do tego problemu.
A co tak naprawdę kupujesz za tę dodatkową gadatliwość? Ile razy naprawdę miałeś problemy, gdy ktoś umieścił liczbę całkowitą w kolekcji, która powinna zawierać ciągi, lub gdy ktoś próbował wyciągnąć ciąg znaków ze zbioru liczb całkowitych? W moim 10-letnim doświadczeniu w tworzeniu komercyjnych aplikacji Java nigdy nie było to dużym źródłem błędów. Nie jestem więc pewien, co otrzymujesz za dodatkową szczegółowość. Wydaje mi się, że to dodatkowy bagaż biurokratyczny.
Teraz będę naprawdę kontrowersyjny. To, co uważam za największy problem z kolekcjami w Javie 1.4, to konieczność pisania wszędzie. Postrzegam te typy jako dodatkowe, rozwlekłe okrucieństwo, które ma wiele tych samych problemów, co generyczne. Na przykład nie mogę tak po prostu zrobić
List someList = someMap.get("some key");
muszę zrobić
List someList = (List) someMap.get("some key");
Powodem jest oczywiście to, że get () zwraca obiekt będący nadtypem listy. Dlatego przypisanie nie może zostać wykonane bez rzutowania. Ponownie zastanów się, ile naprawdę kupuje ta reguła. Z mojego doświadczenia niewiele.
Myślę, że Java byłaby znacznie lepsza, gdyby 1) nie dodała typów ogólnych, ale 2) zamiast tego umożliwiła niejawne rzutowanie z nadtypu na podtyp. Pozwól na wychwycenie nieprawidłowych rzutów w czasie wykonywania Wtedy mógłbym mieć prostotę definiowania
Map someMap;
a później robię
List someList = someMap.get("some key");
całe okrucieństwo by zniknęło i naprawdę nie sądzę, żebym wprowadzał nowe duże źródło błędów do mojego kodu.
Innym efektem ubocznym ich kompilacji, a nie czasu wykonywania, jest to, że nie można wywołać konstruktora typu ogólnego. Więc nie możesz ich użyć do wdrożenia generycznej fabryki ...
public class MyClass {
public T getStuff() {
return new T();
}
}
--jeffk ++
Typy generyczne Java są sprawdzane pod kątem poprawności w czasie kompilacji, a następnie wszystkie informacje o typie są usuwane (proces ten nazywa się wymazywaniem typu . W ten sposób rodzaj ogólny List<Integer>
zostanie zredukowany do jego surowego typu , nieogólnego List
, który może zawierać obiekty dowolnej klasy.
Powoduje to możliwość wstawiania dowolnych obiektów do listy w czasie wykonywania, a także nie można teraz stwierdzić, jakie typy były używane jako parametry ogólne. To z kolei skutkuje
ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if(li.getClass() == lf.getClass()) // evaluates to true
System.out.println("Equal");
Chciałbym, żeby to była wiki, żebym mógł dodać ją innym ludziom ... ale ...
Problemy:
<
? Rozszerza MyObject >
[], ale nie mogę)Ignorując cały bałagan związany z wymazywaniem typów, określone typy generyczne po prostu nie działają.
To kompiluje:
List<Integer> x = Collections.emptyList();
Ale to jest błąd składniowy:
foo(Collections.emptyList());
Gdzie foo jest zdefiniowane jako:
void foo(List<Integer> x) { /* method body not important */ }
Zatem to, czy typ wyrażenia sprawdza, zależy od tego, czy jest przypisywany do zmiennej lokalnej, czy do rzeczywistego parametru wywołania metody. Jak szalone to jest?
Wprowadzenie typów ogólnych do języka Java było trudnym zadaniem, ponieważ architekci starali się zrównoważyć funkcjonalność, łatwość użycia i zgodność wsteczną ze starszym kodem. Dość oczekiwano, że trzeba było pójść na kompromisy.
Są też tacy, którzy uważają, że implementacja typów generycznych w Javie zwiększyła złożoność języka do niedopuszczalnego poziomu (patrz „ Generics Consented Harmful ” Kena Arnolda ). Często zadawane pytania dotyczące generycznych Angeliki Langer dają całkiem dobry pogląd na to, jak skomplikowane mogą się stać.
Java nie wymusza typów ogólnych w czasie wykonywania, tylko w czasie kompilacji.
Oznacza to, że możesz robić interesujące rzeczy, takie jak dodawanie niewłaściwych typów do ogólnych kolekcji.
Typy Java są kompilowane tylko w czasie kompilacji i są kompilowane do kodu innego niż rodzajowy. W języku C # rzeczywisty skompilowany plik MSIL jest ogólny. Ma to ogromny wpływ na wydajność, ponieważ Java nadal przesyła dane w czasie wykonywania. Więcej tutaj .
Jeśli posłuchasz Java Posse # 279 - Interview with Joe Darcy i Alex Buckley , rozmawiają o tym problemie. To również prowadzi do posta na blogu Neal Gafter zatytułowanego Reified Generics for Java, który mówi:
Wiele osób jest niezadowolonych z ograniczeń wynikających ze sposobu implementacji typów generycznych w Javie. W szczególności są niezadowoleni, że parametry typu ogólnego nie są reifikowane: nie są dostępne w czasie wykonywania. Typy ogólne są implementowane przy użyciu wymazywania, w którym parametry typu ogólnego są po prostu usuwane w czasie wykonywania.
Ten wpis na blogu odnosi się do starszego wpisu, Puzzling Through Erasure: answer , w którym podkreślono kwestię zgodności migracji w wymaganiach.
Celem było zapewnienie kompatybilności wstecznej zarówno kodu źródłowego, jak i wynikowego, a także kompatybilności migracji.