Przeczytałem o usuwaniu typu Java na stronie Oracle .
Kiedy następuje usunięcie typu? W czasie kompilacji czy w czasie wykonywania? Kiedy klasa jest załadowana? Kiedy instancja klasy jest tworzona?
Wiele stron (w tym oficjalny samouczek wspomniany powyżej) twierdzi, że kasowanie typu występuje podczas kompilacji. Jeśli informacje o typie zostaną całkowicie usunięte w czasie kompilacji, w jaki sposób JDK sprawdza zgodność typu, gdy wywoływana jest metoda wykorzystująca ogólne, bez informacji o typie lub informacji o niewłaściwym typie?
Rozważmy następujący przykład: klasa Say A
ma metody empty(Box<? extends Number> b)
. Kompilujemy A.java
i otrzymujemy plik klasy A.class
.
public class A {
public static void empty(Box<? extends Number> b) {}
}
public class Box<T> {}
Teraz możemy utworzyć inną klasę B
, która wywołuje metodę empty
z non-parametryzowanego argumentu typu (RAW) empty(new Box())
. Jeśli kompilujemy B.java
się A.class
w ścieżce klas, javac jest wystarczająco inteligentny, aby wywołać ostrzeżenie. Więc A.class
ma jakieś informacje o typie przechowywanych w nim.
public class B {
public static void invoke() {
// java: unchecked method invocation:
// method empty in class A is applied to given types
// required: Box<? extends java.lang.Number>
// found: Box
// java: unchecked conversion
// required: Box<? extends java.lang.Number>
// found: Box
A.empty(new Box());
}
}
Domyślam się, że kasowanie typu ma miejsce, gdy klasa jest ładowana, ale to tylko przypuszczenie. Kiedy to się stanie?
źródło
Odpowiedzi:
Kasowanie typu ma zastosowanie w przypadku generycznych. W pliku klasy zdecydowanie są metadane pozwalające stwierdzić, czy metoda / typ jest ogólna, jakie są ograniczenia itp. Ale kiedy używane są ogólne, są one przekształcane w kontrole czasu kompilacji i rzutowania w czasie wykonywania. Więc ten kod:
jest wkompilowany w
W czasie wykonywania nie ma możliwości sprawdzenia, czy
T=String
dla obiektu listy - ta informacja zniknęła.... ale
List<T>
sam interfejs wciąż reklamuje się jako ogólny.EDYCJA: Dla wyjaśnienia, kompilator zachowuje informacje o zmiennej, która jest
List<String>
- ale nadal nie możesz tego znaleźćT=String
dla samego obiektu listy.źródło
List<? extends InputStream>
skąd możesz wiedzieć, jaki to był typ, kiedy został utworzony? Nawet jeśli można dowiedzieć się typ pola odniesienia zostały zawarte w, dlaczego trzeba? Dlaczego powinieneś mieć możliwość uzyskania wszystkich pozostałych informacji o obiekcie w czasie wykonywania, ale nie jego argumentów typu ogólnego? Wygląda na to, że próbujesz wymazać czcionkę, aby była to drobiazg, który tak naprawdę nie wpływa na programistów - podczas gdy w niektórych przypadkach jest to bardzo poważny problem.Kompilator odpowiada za zrozumienie Generics w czasie kompilacji. Kompilator jest także odpowiedzialny za odrzucenie tego „zrozumienia” klas ogólnych, w procesie, który nazywamy skasowaniem typu . Wszystko dzieje się w czasie kompilacji.
Uwaga: W przeciwieństwie do przekonań większości programistów Java, możliwe jest zachowanie informacji o czasie kompilacji i pobieranie tych informacji w czasie wykonywania, mimo że w bardzo ograniczony sposób. Innymi słowy: Java zapewnia zreifikowane generyki w bardzo ograniczony sposób .
Jeśli chodzi o usuwanie typu
Należy zauważyć, że, w czasie kompilacji, kompilator posiada pełną informację o typie dostępne, ale ta informacja jest celowo spadła w ogóle , gdy kod bajtowy jest generowany w procesie znanym jako typ skasowaniem . Odbywa się to w ten sposób ze względu na problemy ze zgodnością: Intencją projektantów języków było zapewnienie pełnej zgodności kodu źródłowego i pełnej zgodności kodu bajtowego między wersjami platformy. Jeśli zostałby zaimplementowany inaczej, musiałbyś ponownie skompilować starsze aplikacje podczas migracji do nowszych wersji platformy. W ten sposób wszystkie sygnatury metod są zachowywane (zgodność kodu źródłowego) i nie trzeba niczego rekompilować (zgodność binarna).
Odnośnie zweryfikowanych generycznych w Javie
Jeśli chcesz zachować informacje o czasie kompilacji, musisz zastosować anonimowe klasy. Chodzi o to: w bardzo szczególnym przypadku anonimowych klas możliwe jest uzyskanie pełnej informacji o czasie kompilacji w czasie wykonywania, co oznacza innymi słowy: zreifikowane generyczne. Oznacza to, że kompilator nie wyrzuca informacji o typie, gdy zaangażowane są anonimowe klasy; informacje te są przechowywane w wygenerowanym kodzie binarnym, a system wykonawczy pozwala na ich odzyskanie.
Napisałem artykuł na ten temat:
https://rgomes.info/using-typetokens-to-retrieve-generic-parameters/
Uwaga na temat techniki opisanej w powyższym artykule jest taka, że dla większości programistów technika ta jest mało znana. Pomimo tego, że działa i działa dobrze, większość programistów czuje się zagubiona lub niewygodna w tej technice. Jeśli masz wspólną bazę kodów lub planujesz opublikować swój kod publicznie, nie polecam powyższej techniki. Z drugiej strony, jeśli jesteś jedynym użytkownikiem swojego kodu, możesz skorzystać z mocy, jaką daje Ci ta technika.
Przykładowy kod
W powyższym artykule znajdują się linki do przykładowego kodu.
źródło
new Box<String>() {};
nie w przypadku dostępu pośredniego,void foo(T) {...new Box<T>() {};...}
ponieważ kompilator nie zachowuje informacji o typie dla deklaracja metody załączającej.Jeśli masz pole typu ogólnego, jego parametry typu są kompilowane do klasy.
Jeśli masz metodę, która pobiera lub zwraca typ ogólny, parametry tego typu są kompilowane do klasy.
Ta informacja jest tym, czego używa kompilator, aby powiedzieć, że nie można przekazać metody
Box<String>
doempty(Box<T extends Number>)
metody.API jest skomplikowane, ale można sprawdzić tę informację poprzez API typ refleksji z metod, takich jak
getGenericParameterTypes
,getGenericReturnType
i na polachgetGenericType
.Jeśli masz kod, który używa typu ogólnego, kompilator wstawia rzutowania w razie potrzeby (w programie wywołującym) w celu sprawdzenia typów. Same obiekty ogólne są tylko typem surowym; sparametryzowany typ jest „kasowany”. Tak więc podczas tworzenia
new Box<Integer>()
nie ma informacji oInteger
klasie wBox
obiekcie.Często zadawane pytania Angeliki Langer to najlepsze referencje, jakie widziałem dla Java Generics.
źródło
Ogólne w języku Java jest naprawdę dobrym przewodnikiem na ten temat.
Tak więc jest w czasie kompilacji. JVM nigdy nie będzie wiedział, którego
ArrayList
użyłeś.Poleciłbym również odpowiedź pana Skeeta na temat: Jaka jest koncepcja wymazywania w generics w Javie?
źródło
Usuwanie typu następuje w czasie kompilacji. Skasowanie typu oznacza, że zapomni on o typie ogólnym, a nie o każdym typie. Poza tym nadal będą metadane dotyczące typów ogólnych. Na przykład
jest konwertowany na
w czasie kompilacji. Możesz otrzymać ostrzeżenia nie dlatego, że kompilator wie o typie generycznym, ale wręcz przeciwnie, ponieważ nie wie wystarczająco, więc nie może zagwarantować bezpieczeństwa typu.
Ponadto kompilator zachowuje informacje o typie parametrów w wywołaniu metody, które można pobrać przez odbicie.
Ten przewodnik jest najlepszym, jaki znalazłem na ten temat.
źródło
Termin „usuwanie typu” nie jest tak naprawdę poprawnym opisem problemu Javy z ogólnymi. Usuwanie typu nie jest samo w sobie złe, w rzeczywistości jest bardzo niezbędne do wydajności i często jest używane w kilku językach, takich jak C ++, Haskell, D.
Przed wstrętem przypomnij sobie poprawną definicję usuwania typu z Wiki
Co to jest kasowanie typu?
Usuwanie typu oznacza wyrzucanie znaczników typu utworzonych w czasie projektowania lub wywnioskowanych znaczników typu w czasie kompilacji, tak aby skompilowany program w kodzie binarnym nie zawierał żadnych znaczników typu. Dotyczy to każdego języka programowania kompilującego się do kodu binarnego, z wyjątkiem niektórych przypadków, w których potrzebne są tagi środowiska wykonawczego. Te wyjątki obejmują na przykład wszystkie typy egzystencjalne (typy referencyjne Java, które są podtypów, dowolny typ w wielu językach, typy Unii). Przyczyną kasowania typów jest to, że programy przekształcają się w język, który jest w pewnym sensie jednoznaczny (język binarny dopuszcza tylko bity), ponieważ typy są jedynie abstrakcjami i zapewniają strukturę dla swoich wartości oraz odpowiednią semantykę do ich obsługi.
To jest w zamian normalna naturalna rzecz.
Problem Javy jest inny i wynika z tego, jak się rozwiąże.
Często składane oświadczenia o Javie, które nie mają potwierdzonych nazw ogólnych, są również błędne.
Java naprawia, ale w niewłaściwy sposób ze względu na kompatybilność wsteczną.
Co to jest Reifikacja?
Z naszej Wiki
Reification oznacza przekształcenie czegoś abstrakcyjnego (typ parametryczny) w coś konkretnego (typ betonowy) przez specjalizację.
Ilustrujemy to prostym przykładem:
ArrayList z definicją:
jest abstrakcją, w szczególności konstruktorem typów, który zostaje „zweryfikowany” po specjalizacji z konkretnym typem, powiedzmy Integer:
gdzie
ArrayList<Integer>
naprawdę jest typ.Ale właśnie tego nie robi Java !!! zamiast tego reifikują stale swoje abstrakcyjne typy swoimi granicami, tj. wytwarzając ten sam konkretny typ niezależnie od parametrów przekazywanych do specjalizacji:
który jest tutaj weryfikowany za pomocą ukrytego obiektu Object (
ArrayList<T extends Object>
==ArrayList<T>
).Mimo to sprawia, że tablice ogólne są bezużyteczne i powodują dziwne błędy dla typów surowych:
powoduje wiele niejasności jak
odnoszą się do tej samej funkcji:
i dlatego w Javie nie można używać przeciążenia metod ogólnych.
źródło
Spotkałem się z usuwaniem tekstu w Androidzie. W produkcji stosujemy gradle z opcją minify. Po minifikacji mam fatalny wyjątek. Zrobiłem prostą funkcję, aby wyświetlić łańcuch dziedziczenia mojego obiektu:
I są dwa wyniki tej funkcji:
Kod nieobrobiony:
Kod minimalny:
Tak więc w kodzie zminimalizowanym rzeczywiste sparametryzowane klasy są zastępowane typami klas surowych bez żadnych informacji o typie. Jako rozwiązanie dla mojego projektu usunąłem wszystkie wywołania refleksji i odpowiedziałem im jawnymi typami par przekazanymi w argumentach funkcji.
źródło