Klasa Sub
jest podklasą klasy Sup
. Co to znaczy praktycznie? Lub innymi słowy, jakie jest praktyczne znaczenie „dziedzictwa”?
Opcja 1: Kod z Sup jest praktycznie kopiowany do Sub. (jak w „kopiuj-wklej”, ale bez skopiowanego kodu widocznego wizualnie w podklasie).
Przykład: methodA()
jest metodą pierwotnie w Sup. Sub rozszerza Sup, więc methodA()
jest (praktycznie) kopiowany do Sub. Teraz Sub ma metodę o nazwie methodA()
. Jest identyczny z Sup methodA()
w każdym wierszu kodu, ale całkowicie należy do Sub - i nie zależy od Sup lub jest w jakikolwiek sposób związany z Sup.
Opcja 2: Kod z Sup nie jest w rzeczywistości kopiowany do Sub. Nadal jest tylko w nadklasie. Ale do tego kodu można uzyskać dostęp za pośrednictwem podklasy i może być on używany przez podklasę.
Przykład: methodA()
jest metodą opisaną w Sup. Sub rozciąga Sup, więc teraz methodA()
można uzyskać poprzez Sub tak: subInstance.methodA()
. Ale to faktycznie wywoła się methodA()
w nadklasie. Co oznacza, że metoda A () będzie działać w kontekście nadklasy, nawet jeśli została wywołana przez podklasę.
Pytanie: Która z dwóch opcji tak naprawdę działa? Jeśli żaden z nich nie jest, proszę opisać, jak te rzeczy faktycznie działają.
źródło
Odpowiedzi:
Opcja 2.
Kod bajtowy jest przywoływany dynamicznie w czasie wykonywania: dlatego na przykład występują błędy LinkageErrors .
Załóżmy na przykład, że skompilujesz dwie klasy:
Teraz zmodyfikuj i ponownie skompiluj klasę nadrzędną bez modyfikacji lub rekompilacji klasy podrzędnej :
Na koniec uruchom program korzystający z klasy potomnej. Otrzymasz NoSuchMethodError :
źródło
Zacznijmy od dwóch prostych klas:
i wtedy
Metoda kompilacji A i patrząc na kod bajtowy otrzymujemy:
I możesz zobaczyć w tym miejscu za pomocą metody invokespecial, która wyszukuje metodę klasy SupAA ().
Invokespecial opcode ma następującą logiką:
W tym przypadku nie ma metody instancji o tej samej nazwie i deskryptorze w swojej klasie, więc pierwsza kula nie będzie strzelać. Druga kula jednak będzie - istnieje nadklasa, która wywołuje metodę superA.
Kompilator nie uwzględnia tego i nie ma kopii źródła Sup w klasie.
Jednak historia nie jest jeszcze skończona. To tylko skompilowany kod. Gdy kod trafi do JVM, HotSpot może się zaangażować.
Niestety nie wiem zbyt wiele na ten temat, więc odwołam się do autorytetu w tej sprawie i przejdę do Inlining w Javie, gdzie mówi się, że HotSpot może wstawiać metody (nawet metody nie-końcowe).
Przechodząc do dokumentacji , należy zauważyć, że jeśli określone wywołanie metody staje się gorącym punktem zamiast wykonywania tego wyszukiwania za każdym razem, informacje te można wstawić - skutecznie kopiując kod z metody Sup A () do metody Sub (A).
Odbywa się to w czasie wykonywania, w pamięci, w oparciu o zachowanie aplikacji i jakie optymalizacje są konieczne, aby przyspieszyć działanie.
Jak stwierdzono w HotSpot Internals dla OpenJDK „Metody są często wprowadzane. Wywołania statyczne, prywatne, końcowe i / lub„ specjalne ”są łatwe do wprowadzenia”.
Jeśli zagłębisz się w opcje JVM , znajdziesz opcję
-XX:MaxInlineSize=35
(domyślnie 35), która jest maksymalną liczbą bajtów, którą można wstawić. Zaznaczę, że właśnie dlatego Java lubi mieć wiele małych metod - ponieważ można je łatwo wprowadzić. Te małe metody stają się szybsze, gdy są nazywane więcej, ponieważ można je wstawiać. I chociaż można grać z tą liczbą i powiększać ją, może to powodować, że inne optymalizacje będą mniej skuteczne. (powiązane pytanie SO: strategia wstawiania HotSpot JIT, która wskazuje na szereg innych opcji, aby zajrzeć do wewnętrznych elementów wstawiania, które robi HotSpot).Zatem nie - kod nie jest wstawiany podczas kompilacji. I tak - kod można bardzo dobrze wprowadzić w czasie wykonywania, jeśli wymagają tego optymalizacje wydajności.
A wszystko, co napisałem o wstawianiu HotSpot dotyczy tylko HotSpot JVM dystrybuowanego przez Oracle. Jeśli spojrzysz na listę wirtualnych maszyn Java w Wikipedii, jest o wiele więcej niż HotSpot, a sposób, w jaki te maszyny JVM obsługują wstawianie, może być zupełnie inny niż to, co opisałem powyżej. Apache Harmony, Dalvik, ART - tam rzeczy mogą działać inaczej.
źródło
kod nie jest kopiowany, jest dostępny poprzez odniesienie:
kompilatory mogą zoptymalizować sposób, w jaki jest to reprezentowane / wykonywane w pamięci, ale to w zasadzie struktura
źródło