Dziedziczenie: czy kod z nadklasy jest praktycznie * kopiowany * do podklasy, czy też jest * określany przez podklasę *?

10

Klasa Subjest 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ą.

Aviv Cohn
źródło
To jest łatwe do przetestowania dla siebie - napisz kod, sprawdź pliki klas (nawet suma kontrolna by to zrobiła), zmodyfikuj superklasę, skompiluj ponownie, ponownie spójrz na pliki klas. Pomocny w zrozumieniu może być również rozdział 3. Kompilacja dla wirtualnej maszyny Java ze specyfikacji JVM (szczególnie sekcja 3.7).
@MichaelT „ wirtualnie skopiowane” to słowo kluczowe. Ponadto, nawet jeśli kod został dosłownie skopiowany, może się to zdarzyć dopiero po załadowaniu klasy.
@ delnan byłoby ciekawe, czy Hotspot (lub inne optymalizatory czasu wykonywania) wstawi kod w pewnym momencie, ale staje się to szczegółem implementacji JVM, który może różnić się między JVM i dlatego nie można poprawnie odpowiedzieć. Najlepsze, co można zrobić, to przyjrzeć się skompilowanemu kodowi bajtowemu (i inokspecjalnemu użyciu kodu operacyjnego, który opisuje, co się faktycznie dzieje)

Odpowiedzi:

13

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:

public class Parent {
  public void doSomething(String x) { ... }
}

public class Child extends Parent {
  @Override
  public void doSomething(String x) {
    super.doSomething(x);
    ...
  }
}

Teraz zmodyfikuj i ponownie skompiluj klasę nadrzędną bez modyfikacji lub rekompilacji klasy podrzędnej :

public class Parent {
  public void doSomething(Collection<?> x) { ... }
}

Na koniec uruchom program korzystający z klasy potomnej. Otrzymasz NoSuchMethodError :

Zgłaszane, jeśli aplikacja próbuje wywołać określoną metodę klasy (statyczną lub instancję), a klasa ta nie ma już definicji tej metody.

Zwykle ten błąd jest wychwytywany przez kompilator; ten błąd może wystąpić tylko w czasie wykonywania, jeśli definicja klasy została niepoprawnie zmieniona.


źródło
7

Zacznijmy od dwóch prostych klas:

package com.michaelt.so.supers;

public class Sup {
    int methodA(int a, int b) {
        return a + b;
    }
}

i wtedy

package com.michaelt.so.supers;

public class Sub extends Sup {
    @Override
    int methodA(int a, int b) {
        return super.methodA(a, b);
    }
}

Metoda kompilacji A i patrząc na kod bajtowy otrzymujemy:

  methodA(II)I
   L0
    LINENUMBER 6 L0
    ALOAD 0
    ILOAD 1
    ILOAD 2
    INVOKESPECIAL com/michaelt/so/supers/Sup.methodA (II)I
    IRETURN
   L1
    LOCALVARIABLE this Lcom/michaelt/so/supers/Sub; L0 L1 0
    LOCALVARIABLE a I L0 L1 1
    LOCALVARIABLE b I L0 L1 2
    MAXSTACK = 3
    MAXLOCALS = 3

I możesz zobaczyć w tym miejscu za pomocą metody invokespecial, która wyszukuje metodę klasy SupAA ().

Invokespecial opcode ma następującą logiką:

  • Jeśli C zawiera deklarację dla metody instancji o tej samej nazwie i deskryptorze co metoda rozstrzygnięta, wówczas ta metoda zostanie wywołana. Procedura wyszukiwania kończy się.
  • W przeciwnym razie, jeśli C ma nadklasę, ta sama procedura wyszukiwania jest wykonywana rekurencyjnie przy użyciu bezpośredniej nadklasy C. Metoda, która ma zostać wywołana, jest wynikiem rekurencyjnego wywołania tej procedury wyszukiwania.
  • W przeciwnym razie wywoływany jest błąd AbstractMethodError.

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.

Społeczność
źródło
0

kod nie jest kopiowany, jest dostępny poprzez odniesienie:

  • podklasa odwołuje się do swoich metod i nadklasy
  • nadklasa odwołuje się do swoich metod

kompilatory mogą zoptymalizować sposób, w jaki jest to reprezentowane / wykonywane w pamięci, ale to w zasadzie struktura

Steven A. Lowe
źródło