Przeczytałem dzisiaj tweeta, który powiedział:
To zabawne, gdy użytkownicy Javy narzekają na wymazywanie typów, co jest jedyną rzeczą, którą Java zrobiła dobrze, ignorując wszystkie rzeczy, które popełniły błąd.
Zatem moje pytanie brzmi:
Czy są korzyści z wymazywania typów w Javie? Jakie korzyści techniczne lub styl programowania oferuje (prawdopodobnie) poza preferencją implementacji JVM w zakresie wstecznej kompatybilności i wydajności w czasie wykonywania?
java
type-erasure
vertti
źródło
źródło
Odpowiedzi:
Typ wymazywania jest dobry
Trzymajmy się faktów
Wiele dotychczasowych odpowiedzi jest nadmiernie zaniepokojonych użytkownikami Twittera. Warto skupić się na wiadomościach, a nie na posłańcu. Jest dość spójna wiadomość, zawierająca nawet tylko wspomniane dotąd fragmenty:
Cel: rozsądne programy
Te tweety odzwierciedlają perspektywę, która nie jest zainteresowana tym, czy możemy zmusić maszynę do zrobienia czegoś , ale bardziej tym, czy możemy uzasadnić, że maszyna zrobi coś, czego naprawdę chcemy. Dobre rozumowanie jest tego dowodem. Dowody można określić za pomocą notacji formalnej lub mniej formalnej. Niezależnie od języka specyfikacji muszą być jasne i rygorystyczne. Nieformalne specyfikacje nie są niemożliwe do poprawnej struktury, ale często zawierają błędy w praktycznym programowaniu. W końcu otrzymujemy środki zaradcze, takie jak testy automatyczne i eksploracyjne, aby nadrobić problemy, które mamy z nieformalnym rozumowaniem. Nie oznacza to, że testowanie jest z natury złym pomysłem, ale cytowany użytkownik Twittera sugeruje, że istnieje znacznie lepszy sposób.
Dlatego naszym celem jest posiadanie poprawnych programów, co do których możemy jasno i rygorystycznie rozumować, w sposób odpowiadający temu, jak maszyna faktycznie wykona program. Nie jest to jednak jedyny cel. Chcemy również, aby nasza logika miała pewien stopień ekspresji. Na przykład, tylko tyle możemy wyrazić za pomocą logiki zdań. Fajnie jest mieć uniwersalną (∀) i egzystencjalną (∃) kwantyfikację z czegoś w rodzaju logiki pierwszego rzędu.
Używanie systemów typów do rozumowania
Te cele można bardzo dobrze rozwiązać za pomocą systemów typów. Jest to szczególnie jasne ze względu na korespondencję Curry-Howard . Tę zgodność często wyraża się następującą analogią: typy są do programów, tak jak twierdzenia do dowodów.
Ta korespondencja jest dość głęboka. Możemy przyjąć logiczne wyrażenia i przetłumaczyć je poprzez korespondencję na typy. Następnie, jeśli mamy program o tej samej sygnaturze typu, który kompiluje, udowodniliśmy, że wyrażenie logiczne jest uniwersalnie prawdziwe (tautologia). Dzieje się tak, ponieważ korespondencja jest dwukierunkowa. Transformacja między typem / programem a światami twierdzeń / dowodów jest mechaniczna iw wielu przypadkach może być zautomatyzowana.
Curry-Howard dobrze wpisuje się w to, co chcielibyśmy zrobić ze specyfikacjami programu.
Czy systemy typów są przydatne w Javie?
Nawet rozumiejąc Curry-Howard, niektórym osobom łatwo jest zlekceważyć wartość systemu czcionek, gdy
Jeśli chodzi o pierwszy punkt, być może IDE sprawiają, że system typów Javy jest wystarczająco łatwy w obsłudze (to wysoce subiektywne).
Jeśli chodzi o drugi punkt, zdarza się, że Java prawie odpowiada logice pierwszego rzędu. Typy generyczne wykorzystują system typów równoważny uniwersalnej kwantyfikacji. Niestety, symbole wieloznaczne dają nam tylko niewielki ułamek kwantyfikacji egzystencjalnej. Ale uniwersalna kwantyfikacja to całkiem dobry początek. Fajnie jest móc powiedzieć, że funkcje
List<A>
działają uniwersalnie dla wszystkich możliwych list, ponieważ A jest całkowicie nieograniczony. Prowadzi to do tego, o czym mówi użytkownik Twittera w odniesieniu do „parametryczności”.Często cytowanym artykułem na temat parametryczności jest bezpłatne twierdzenie Philipa Wadlera ! . Interesujące w tym artykule jest to, że na podstawie samego podpisu typu możemy udowodnić kilka bardzo interesujących niezmienników. Gdybyśmy mieli napisać testy automatyczne dla tych niezmienników, marnowalibyśmy bardzo czas. Na przykład for
List<A>
, z samego podpisu typu forflatten
możemy to uzasadnić
To prosty przykład i prawdopodobnie możesz uzasadnić to nieformalnie, ale jest jeszcze ładniej, gdy otrzymamy takie dowody formalnie za darmo z systemu typów i sprawdzone przez kompilator.
Brak usunięcia może prowadzić do nadużyć
Z punktu widzenia implementacji języka, typy generyczne Javy (które odpowiadają typom uniwersalnym) wpływają bardzo mocno na parametryczność wykorzystywaną do uzyskiwania dowodów na to, co robią nasze programy. To prowadzi do trzeciego wspomnianego problemu. Wszystkie te zyski dowodu i poprawności wymagają systemu typu dźwiękowego zaimplementowanego bez wad. Java z pewnością ma pewne funkcje językowe, które pozwalają nam zniszczyć nasze rozumowanie. Należą do nich między innymi:
Nieusunięte rodzaje ogólne są pod wieloma względami powiązane z refleksją. Bez wymazywania istnieją informacje o czasie wykonywania, które są przenoszone z implementacją, których możemy użyć do zaprojektowania naszych algorytmów. Oznacza to, że statycznie, gdy wnioskujemy o programach, nie mamy pełnego obrazu. Odbicie poważnie zagraża poprawności wszelkich dowodów, o których wnioskujemy statycznie. To nie przypadek, że refleksja prowadzi również do różnych trudnych defektów.
Więc w jaki sposób nieusunięte typy generyczne mogą być „przydatne”? Rozważmy użycie wspomniane w tweecie:
Co się stanie, jeśli T nie ma konstruktora bez argonu? W niektórych językach otrzymujesz wartość zerową. A może pomijasz wartość null i przechodzisz od razu do zgłaszania wyjątku (do którego wartości null wydają się prowadzić). Ponieważ nasz język jest kompletny w Turingu, nie można wywnioskować, które wywołania
broken
będą obejmować „bezpieczne” typy z konstruktorami bezargumentowymi, a które nie. Straciliśmy pewność, że nasz program działa uniwersalnie.Wymazywanie oznacza, że uzasadniliśmy (więc wymażmy)
Jeśli więc chcemy uzasadniać nasze programy, stanowczo odradzamy stosowanie funkcji językowych, które silnie zagrażają naszemu rozumowaniu. Kiedy już to zrobimy, dlaczego nie po prostu porzucić typów w czasie wykonywania? Nie są potrzebne. Możemy uzyskać pewną wydajność i prostotę z satysfakcją, że żadne rzutowanie nie zawiedzie lub że podczas wywołania może brakować metod.
Wymazywanie zachęca do rozumowania.
źródło
Typy to konstrukcja używana do pisania programów w sposób, który umożliwia kompilatorowi sprawdzenie poprawności programu. Typ to propozycja wartości - kompilator sprawdza, czy ta propozycja jest prawdziwa.
Podczas wykonywania programu nie powinno być potrzeby podawania informacji o typie - zostało to już zweryfikowane przez kompilator. Kompilator powinien mieć możliwość odrzucenia tych informacji w celu optymalizacji kodu - przyspieszenia jego działania, wygenerowania mniejszego pliku binarnego itp. Ułatwia to wymazanie parametrów typu.
Java przerywa statyczne typowanie, umożliwiając odpytywanie informacji o typie w czasie wykonywania - odbicie, instancja itp. Pozwala to na tworzenie programów, których nie można zweryfikować statycznie - omijają one system typów. Pomija również możliwości statycznej optymalizacji.
Fakt, że parametry typu są usuwane, uniemożliwia skonstruowanie niektórych wystąpień tych niepoprawnych programów, jednak bardziej niepoprawne programy byłyby niedozwolone, gdyby usunięto więcej informacji o typie, a elementy odbicia i wystąpienia obiektów zostały usunięte.
Wymazywanie jest ważne dla zachowania właściwości „parametryczności” typu danych. Powiedzmy, że mam sparametryzowany typ „Lista” nad typem składnika T. tj. List <T>. Ten typ jest propozycją, że ten typ List działa identycznie dla każdego typu T. Fakt, że T jest abstrakcyjnym, nieograniczonym parametrem typu oznacza, że nic nie wiemy o tym typie, dlatego nie możemy robić niczego specjalnego dla specjalnych przypadków T.
np. powiedz, że mam List xs = asList ("3"). Dodaję element: xs.add ("q"). W końcu otrzymuję ["3", "q"]. Ponieważ jest to parametryczne, mogę założyć, że List xs = asList (7); xs.add (8) kończy się na [7,8] Wiem z typu, że nie robi jednej rzeczy dla String, a jednej dla Int.
Ponadto wiem, że funkcja List.add nie może wymyślać wartości T z powietrza. Wiem, że jeśli do mojej asList („3”) dodano „7”, jedyne możliwe odpowiedzi byłyby konstruowane z wartości „3” i „7”. Nie ma możliwości dodania „2” lub „z” do listy, ponieważ funkcja nie mogłaby jej skonstruować. Żadna z tych innych wartości nie byłaby rozsądna do dodania, a parametryczność zapobiega konstruowaniu tych nieprawidłowych programów.
Zasadniczo kasowanie zapobiega jakimś sposobom naruszenia parametryczności, a tym samym eliminuje możliwości błędnych programów, co jest celem statycznego pisania.
źródło
(Chociaż już tutaj napisałem odpowiedź, wracając do tego pytania dwa lata później, zdaję sobie sprawę, że istnieje inny, zupełnie inny sposób odpowiedzi, więc pozostawiam poprzednią odpowiedź nietkniętą i dodam tę.)
Jest wysoce dyskusyjne, czy proces wykonywany w Javie Generics zasługuje na miano „wymazywania typu”. Ponieważ typy ogólne nie są usuwane, ale zastępowane ich surowymi odpowiednikami, lepszym wyborem wydaje się być „okaleczanie typów”.
Kwintesencją funkcji wymazywania typów w powszechnie rozumianym sensie jest wymuszanie, aby środowisko wykonawcze pozostawało w granicach statycznego systemu typów, czyniąc go „ślepym” na strukturę danych, do których uzyskuje dostęp. Daje to pełną moc kompilatorowi i pozwala mu dowodzić twierdzeń na podstawie samych typów statycznych. Pomaga również programiście, ograniczając stopnie swobody kodu, dając więcej mocy prostemu rozumowaniu.
Wymazywanie typu w Javie tego nie daje - uszkadza kompilator, jak w tym przykładzie:
(Powyższe dwie deklaracje zwijają się do tego samego podpisu metody po usunięciu).
Z drugiej strony środowisko wykonawcze może nadal sprawdzać typ obiektu i uzasadniać go, ale ponieważ jego wgląd w prawdziwy typ jest utrudniony przez wymazanie, naruszenia typu statycznego są trywialne do osiągnięcia i trudne do uniknięcia.
Aby jeszcze bardziej skomplikować sprawę, oryginalne i usunięte sygnatury typów współistnieją i są rozważane równolegle podczas kompilacji. Dzieje się tak, ponieważ cały proces nie polega na usuwaniu informacji o typie ze środowiska wykonawczego, ale na tworzeniu systemu typów ogólnych w starszym systemie typów surowych w celu zachowania kompatybilności wstecznej. Ten klejnot jest klasycznym przykładem:
(Nadmiarowy
extends Object
musiał zostać dodany, aby zachować wsteczną zgodność skasowanego podpisu).Mając to na uwadze, powróćmy do cytatu:
Co dokładnie poprawiła Java? Czy to samo słowo, niezależnie od znaczenia? Dla kontrastu spójrz na skromny
int
typ: żadne sprawdzenie typu w czasie wykonywania nie jest nigdy wykonywane ani nawet możliwe, a wykonanie jest zawsze całkowicie bezpieczne dla typów. Tak wygląda wymazywanie typów po prawidłowym wykonaniu: nawet nie wiesz, że tam jest.źródło
Jedyną rzeczą, której w ogóle tu nie rozważam, jest to, że polimorfizm OOP w czasie wykonywania jest zasadniczo zależny od reifikacji typów w czasie wykonywania. Kiedy język, którego kręgosłup jest utrzymywany w miejscu przez odmienne typy, wprowadza główne rozszerzenie swojego systemu typów i opiera go na usuwaniu typów, dysonans poznawczy jest nieuniknionym rezultatem. Dokładnie to stało się ze społecznością Java; to dlatego wymazywanie typów wzbudziło tak wiele kontrowersji i ostatecznie planuje się cofnąć to w przyszłej wersji Javy . Znalezienie czegoś zabawnego w tej skardze użytkowników Javy zdradza albo szczere niezrozumienie ducha Javy, albo świadomy żart.
Twierdzenie „wymazanie jest jedyną rzeczą, którą Java ma rację” sugeruje twierdzenie, że „wszystkie języki oparte na dynamicznej wysyłce w porównaniu z argumentem funkcji typu runtime są zasadniczo wadliwe”. Chociaż samo w sobie z pewnością jest to uzasadnione twierdzenie, które można nawet uznać za uzasadnioną krytykę wszystkich języków OOP, w tym Javy, nie może stanowić punktu kluczowego, z którego można oceniać i krytykować funkcje w kontekście Javy , gdzie polimorfizm w czasie wykonywania jest aksjomatyczna.
Podsumowując, chociaż można słusznie stwierdzić, że „usuwanie typów jest sposobem na projektowanie języka”, pozycje obsługujące wymazywanie typów w Javie są niewłaściwe po prostu dlatego, że jest na to dużo, dużo za późno i tak było już w historycznym momencie kiedy Oak został objęty przez Sun i przemianowany na Java.
Jeśli chodzi o to, czy statyczne typowanie samo w sobie jest właściwym kierunkiem w projektowaniu języków programowania, pasuje to do znacznie szerszego kontekstu filozoficznego tego, co uważamy za aktywność programowania . Jedna szkoła myślenia, wyraźnie wywodząca się z klasycznej tradycji matematyki, postrzega programy jako przykłady jednej lub drugiej koncepcji matematycznej (zdania, funkcje itp.), Ale istnieje zupełnie inna klasa podejść, które postrzegają programowanie jako sposób na porozmawiaj z maszyną i wyjaśnij, czego od niej chcemy. Z tego punktu widzenia program jest dynamiczną, organicznie rozwijającą się istotą, dramatycznym przeciwieństwem starannie wzniesionej edycji programu zapisanego statycznie.
Wydawałoby się naturalne traktowanie języków dynamicznych jako kroku w tym kierunku: spójność programu wyłania się oddolnie, bez żadnych stałych a priori, które narzucałyby go odgórnie. Ten paradygmat można postrzegać jako krok w kierunku modelowania procesu, w którym my, ludzie, stajemy się tym, czym jesteśmy, poprzez rozwój i uczenie się.
źródło
Kolejny post tego samego użytkownika w tej samej rozmowie:
(Było to odpowiedzią na oświadczenie innego użytkownika, a mianowicie, że „wydaje się, że w niektórych sytuacjach„ nowe T ”byłoby lepsze”, przy czym pomysł
new T()
jest niemożliwy z powodu wymazania typu. (Jest to dyskusyjne - nawet jeśliT
były dostępne pod adresem środowiska uruchomieniowego, może to być klasa abstrakcyjna lub interfejs, może to byćVoid
, lub może brakować konstruktora no-arg, albo jego konstruktor no-arg może być prywatny (np. ponieważ przypuszczalnie jest to klasa pojedyncza) lub Konstruktor no-arg może określić sprawdzony wyjątek, którego metoda ogólna nie wychwytuje ani nie określa - ale taka była przesłanka. Niezależnie od tego, prawdą jest, że bez wymazywania można przynajmniej napisaćT.class.newInstance()
, który obsługuje te problemy.))Ten pogląd, że typy są izomorficzne ze zdaniami, sugeruje, że użytkownik ma podstawy w formalnej teorii typów. (S) najprawdopodobniej nie lubi "typów dynamicznych" lub "typów uruchomieniowych" i wolałby Javę bez ograniczeń
instanceof
i refleksji i tak dalej. (Pomyśl o języku takim jak Standard ML, który ma bardzo bogaty (statyczny) system typów i którego dynamiczna semantyka nie zależy od żadnych informacji o typach.)Warto pamiętać, przy okazji, że użytkownik trolluje: podczas gdy on (e) prawdopodobnie szczerze woli (statycznie) typowane języki, (s) nie próbuje szczerze przekonać innych do tego poglądu. Głównym celem oryginalnego tweeta było raczej wyśmiewanie tych, którzy się nie zgadzają, a po tym, jak niektórzy z nich się nie zgadzają, użytkownik opublikował kolejne tweety, takie jak „powodem, dla którego java wymazuje typ, jest to, że Wadler i inni wiedzą, co robią, w przeciwieństwie do użytkowników java ”. Niestety, utrudnia to ustalenie, o czym właściwie myśli; ale na szczęście prawdopodobnie oznacza to, że nie jest to bardzo ważne. Ludzie z rzeczywistą głębią swoich poglądów na ogół nie uciekają się do trolli, które są dość pozbawione treści.
źródło
Jedną dobrą rzeczą jest to, że nie było potrzeby zmiany maszyny JVM, gdy wprowadzono produkty generyczne. Java implementuje typy ogólne tylko na poziomie kompilatora.
źródło
Powodem, dla którego wymazywanie typu jest dobre, jest to, że rzeczy, które uniemożliwia, są szkodliwe. Zapobieganie inspekcji argumentów typu w czasie wykonywania ułatwia zrozumienie i wnioskowanie na temat programów.
Spostrzeżenie, które uznałem za nieco sprzeczne z intuicją, jest takie, że kiedy sygnatury funkcji są bardziej ogólne, stają się łatwiejsze do zrozumienia. Dzieje się tak, ponieważ zmniejsza się liczba możliwych implementacji. Rozważ metodę z tym podpisem, o której wiemy, że nie ma skutków ubocznych:
Jakie są możliwe implementacje tej funkcji? Bardzo dużo. Niewiele można powiedzieć o tym, co robi ta funkcja. Może to być odwrócenie listy wejść. Może to być łączenie ze sobą intów, sumowanie ich i zwracanie listy o połowę mniejszej. Istnieje wiele innych możliwości, które można sobie wyobrazić. Rozważmy teraz:
Ile jest implementacji tej funkcji? Ponieważ implementacja nie może znać typu elementów, można teraz wykluczyć ogromną liczbę implementacji: elementów nie można łączyć, dodawać do listy ani odfiltrowywać i in. Jesteśmy ograniczeni do takich rzeczy, jak: tożsamość (brak zmian na liście), upuszczanie elementów lub odwracanie listy. Ta funkcja jest łatwiejsza do rozważenia na podstawie samego jej podpisu.
Tylko że… w Javie zawsze możesz oszukać system typów. Ponieważ implementacja tej ogólnej metody może używać takich rzeczy, jak
instanceof
sprawdzenia i / lub rzutowania na dowolne typy, nasze rozumowanie oparte na sygnaturze typu można łatwo uczynić bezużytecznymi. Funkcja mogłaby sprawdzić typ elementów i wykonać dowolną liczbę rzeczy w oparciu o wynik. Jeśli te hacki w czasie wykonywania są dozwolone, sparametryzowane sygnatury metod stają się dla nas znacznie mniej przydatne.Gdyby Java nie miała wymazywania typów (to znaczy argumenty typów byłyby reifikowane w czasie wykonywania), to po prostu pozwoliłoby na więcej takich sztuczek utrudniających rozumowanie. W powyższym przykładzie implementacja może naruszyć oczekiwania określone przez sygnaturę typu tylko wtedy, gdy lista zawiera przynajmniej jeden element; ale jeśli
T
został zreifikowany, mógłby to zrobić, nawet jeśli lista byłaby pusta. Typy zreifikowane po prostu zwiększyłyby (już bardzo wiele) możliwości utrudniania nam zrozumienia kodu.Usuwanie typów sprawia, że język jest mniej „potężny”. Ale niektóre formy „władzy” są w rzeczywistości szkodliwe.
źródło
instanceof
typy generyczne są złożone, ale na tym, że rzeczy takie jak rzutowanie i utrudniają nam zdolność rozumowania, co robi kod na podstawie typów. Jeśli Java miałaby zreifikować argumenty typu, tylko pogorszyłoby to problem. Wymazywanie typów w czasie wykonywania powoduje zwiększenie użyteczności systemu typów.To nie jest bezpośrednia odpowiedź (OP zapytał „jakie są korzyści”, a ja odpowiadam „jakie są wady”)
W porównaniu z systemem typu C # wymazywanie typów w Javie jest prawdziwym problemem dla dwóch powodów
Nie możesz dwukrotnie zaimplementować interfejsu
W C # można zaimplementować oba
IEnumerable<T1>
iIEnumerable<T2>
bezpiecznie, zwłaszcza jeśli te dwa typy nie mają wspólnego przodka (tj. Ich przodek jestObject
).Praktyczny przykład: w Spring Framework nie można implementować
ApplicationListener<? extends ApplicationEvent>
wielokrotnie. Jeśli potrzebujesz różnych zachowań w oparciu oT
to, musisz przetestowaćinstanceof
Nie możesz zrobić nowego T ()
(i potrzebujesz odniesienia do Class, aby to zrobić)
Jak komentowali inni, wykonanie odpowiednika
new T()
może być wykonane tylko poprzez odbicie, tylko przez wywołanie wystąpieniaClass<T>
, upewniając się co do parametrów wymaganych przez konstruktora. C # umożliwia tonew T()
tylko w przypadku ograniczeniaT
do konstruktora bez parametrów. JeśliT
nie przestrzega tego ograniczenia, zgłaszany jest błąd kompilacji .W Javie często będziesz zmuszony pisać metody, które wyglądają jak poniżej
Wady powyższego kodu to:
ReflectiveOperationException
jest generowany w czasie wykonywaniaGdybym był autorem C #, wprowadziłbym możliwość określenia jednego lub więcej ograniczeń konstruktora, które są łatwe do zweryfikowania w czasie kompilacji (więc mogę wymagać np. Konstruktora z parametrami
string,string
). Ale ostatnia to spekulacjaźródło
Dodatkowy punkt, który wydaje się być brany pod uwagę w żadnej innej odpowiedzi: jeśli naprawdę potrzebujesz typów ogólnych z pisaniem w czasie wykonywania, możesz zaimplementować to samodzielnie w następujący sposób:
Ta klasa jest następnie w stanie robić wszystkie rzeczy, które byłyby osiągalne domyślnie, gdyby Java nie używała wymazywania: może przydzielać nowe
T
s (zakładając, żeT
ma konstruktor, który pasuje do wzorca, którego oczekuje, że ma użyć) lub tabliceT
s, może dynamicznie testuj w czasie wykonywania, czy określony obiekt jestT
i zmieniaj zachowanie w zależności od tego i tak dalej.Na przykład:
źródło
unika nadmiaru kodu, podobnie jak w C ++, ponieważ ten sam kod jest używany dla wielu typów; jednak wymazywanie typów wymaga wirtualnego wysyłania, podczas gdy podejście C ++ - nadwyżki kodu może wykonywać niewirtualnie wysyłane typy generyczne
źródło
Większość odpowiedzi dotyczy bardziej filozofii programowania niż rzeczywistych szczegółów technicznych.
I chociaż to pytanie ma więcej niż 5 lat, nadal pozostaje pytanie: dlaczego z technicznego punktu widzenia pożądane jest usuwanie czcionek? Ostatecznie odpowiedź jest raczej prosta (na wyższym poziomie): https://en.wikipedia.org/wiki/Type_erasure
Szablony C ++ nie istnieją w czasie wykonywania. Kompilator emituje w pełni zoptymalizowaną wersję dla każdego wywołania, co oznacza, że wykonanie nie zależy od informacji o typie. Ale jak JIT radzi sobie z różnymi wersjami tej samej funkcji? Czy nie byłoby lepiej mieć tylko jedną funkcję? Nie chciałbym, aby JIT musiał optymalizować wszystkie jego różne wersje. Cóż, ale co z bezpieczeństwem typów? Zgadnij, że to musi wyjść przez okno.
Ale chwileczkę: jak to robi .NET? Odbicie! W ten sposób muszą zoptymalizować tylko jedną funkcję, a także uzyskać informacje o typie środowiska wykonawczego. I dlatego generyczne .NET były kiedyś wolniejsze (chociaż stały się znacznie lepsze). Nie twierdzę, że to nie jest wygodne! Ale jest drogi i nie powinien być używany, gdy nie jest to absolutnie konieczne (nie jest uważany za drogi w językach dynamicznie typowanych, ponieważ kompilator / interpreter i tak opiera się na refleksji).
W ten sposób programowanie ogólne z wymazywaniem typu jest bliskie zeru (niektóre sprawdzenia / rzutowania w czasie wykonywania są nadal wymagane): https://docs.oracle.com/javase/tutorial/java/generics/erasure.html
źródło