Ogólne i usuwanie typów

10

Ogólne w Javie są implementowane za pomocą usuwania typu. JLS twierdzi, że inspiracją była kompatybilność wsteczna. Gdzie, jak z drugiej strony, generyczne C # są możliwe do ponownego zdefiniowania.

Teoretycznie jakie są zalety i wady wynikające z tego, że Generics jest „usuwany” lub „możliwy do powtórzenia”?

Czy czegoś brakuje Java?

Jatin
źródło

Odpowiedzi:

8

Motywacja (zgodność wsteczna) jest zarówno zaletą, jak i wadą. Jest to niekorzystne, ponieważ wszyscy wolelibyśmy mieć typy podlegające zwrotowi, ale cena do zapłaty była wysoka. Rozważ wybór projektu w języku C #. Mają zmienne typy, ale teraz mają zduplikowane interfejsy API. Wyobraźmy sobie API Java, w którym mieliśmy również zduplikowane interfejsy API dla każdej sparametryzowanej klasy. Teraz wyobraź sobie, jak przenosisz tysiące wierszy kodu ze starszych klas do nowych klas ogólnych. Kto nie uznałby duplikatów interfejsów API za wadę? Ale hej, mają typy, które można zmienić!

Tak więc główną motywacją była „ewolucja, a nie rewolucja”. I logicznie, każda decyzja ma kompromisy.

Oprócz innych wspomnianych wad możemy dodać fakt, że usunięcie typu może być trudne do uzasadnienia w czasie kompilacji, ponieważ nie jest oczywiste, że niektóre typy zostaną usunięte, a to prowadzi do bardzo dziwnych i trudnych do znalezienia błędów.

Istnienie metod pomostowych (ten kompilator generowany syntaktycznie w celu zachowania zgodności binarnej) można również postrzegać jako wadę. I może to być właśnie jeden z powodów błędów, o których wspomniałem w poprzednim akapicie.

Główne wady wynikają z już oczywistego faktu, że istnieje jedna klasa, a nie wiele klas dla typów ogólnych. Jako kolejny przykład rozważ, że przeciążenie metody tą samą klasą ogólną kończy się niepowodzeniem w Javie:

public void doSomething(List<One>);
public void doSomething(List<Two>);

Coś, co może być postrzegane jako wada typów wielokrotnego użytku (przynajmniej w języku C #), to fakt, że powodują one eksplozję kodu . Na przykład List<int>jest jedna klasa, a a List<double>jest zupełnie inna, ponieważ jest to a List<string>i List<MyType>. Tak więc klasy muszą być zdefiniowane w czasie wykonywania, powodując eksplozję klas i pochłaniając cenne zasoby podczas ich generowania.

Biorąc pod uwagę fakt, że nie jest możliwe zdefiniowanie new T()w Javie, o którym mowa w innej odpowiedzi, interesujące jest również to, że nie jest to tylko kwestia usunięcia typu. Wymaga również istnienia domyślnego konstruktora, dlatego C # wymaga do tego „nowego ograniczenia”. (Zobacz Dlaczego nowa T () nie jest możliwa w Javie , autor: Alex Buckley).

edalorzo
źródło
3
Myślę, że mam rację mówiąc, że w CLR, dla typu referencyjnego T , otrzymujesz tylko jedną kopię Class<T>kodu dla wszystkich Ts; plus dodatkowa kopia dla każdego faktycznie użytego typu wartości T .
AakashM
@AakashM: Jest poprawne, istnieje jedna klasa typu odniesienia na rodzaj ogólny, jednak każdy typ wartości tworzy własną wersję. Wynika to z zapewnienia specjalistycznej przestrzeni dyskowej w zależności od typu, wszystkie odwołania zajmują tę samą pamięć, ale typy wartości zajmują inną pamięć dla każdego typu.
Guvante
6

Minusem kasowania typu jest to, że nie znasz w czasie wykonywania typu ogólnego. Oznacza to, że nie można na nich zastosować refleksji i nie można ich utworzyć w środowisku wykonawczym.

Nie można zrobić czegoś takiego w Javie:

public class MyClass<T> {
    private T t;

    //more methods    
    public void myMethod() {
        //more code
        if (someCondition) {
            t = new T();//illegal
        } else {
            T[] array = new T[];//illegal
        }            
    }       
}

Istnieje obejście tego problemu, ale wymaga ono więcej kodu. Zaletą, jak już wspomniałeś, jest kompatybilność wsteczna.

m3th0dman
źródło
3

Kolejną zaletą usuniętych danych ogólnych jest to, że różne języki kompilujące się do JVM stosują różne strategie dla danych ogólnych, np. Strona definicji Scali kontra kowariancja strony użycia Javy. Także bardziej rodzajowe generały Scali byłyby trudniejsze do obsługi na zmienionych typach .Net, ponieważ zasadniczo Scala na .Net zignorował niekompatybilny format potwierdzania C #. Gdybyśmy zreformowali generyczne w JVM, najprawdopodobniej te zreifikowane generyczne nie byłyby odpowiednie dla funkcji, które naprawdę lubimy w Scali, i utknęlibyśmy z czymś nieoptymalnym. Cytowanie z bloga Oli Bini ,

Oznacza to, że jeśli chcesz dodać do JVM zreifikowane generyczne, powinieneś być bardzo pewien, że ta implementacja może obejmować zarówno wszystkie języki statyczne, które chcą wprowadzać innowacje we własnej wersji generycznych, jak i wszystkie języki dynamiczne, które chcą stworzyć dobrą implementację i przyjemny interfejs z bibliotekami Java. Ponieważ jeśli dodasz sprawdzone generyczne, które nie spełniają tych kryteriów, stłumisz innowacje i znacznie utrudnisz korzystanie z JVM jako wielojęzycznej maszyny wirtualnej.

Osobiście nie uważam konieczności używania TypeTagkontekstu związanego ze Scalą w przypadku sprzecznych przeciążonych metod za wadę, ponieważ przenosi on narzut i brak elastyczności reifikacji z globalnego (całego programu i wszystkich możliwych języków) na stronę użytkowania dla każdego języka problem występuje tylko rzadziej.

Shelby Moore III
źródło
logika nuttycom, dlaczego reifikowane leki generyczne są złe .
Shelby Moore III,
Innym powodem faworyzowania usuniętych generyków jest to, że zależności należy wstrzykiwać w sposób uwzględniający rozwiązanie problemu wyrażenia. Jeśli testujesz konkretne przypadki typów lub zakodowałeś fabrycznie w swojej funkcji ogólnej, to źle robisz rozszerzalności.
Shelby Moore III,