Vererbung: Wird Code aus der Oberklasse virtuell in die Unterklasse * kopiert * oder wird er von der Unterklasse * bezeichnet *?

10

Klasse Subist eine Unterklasse der Klasse Sup. Was bedeutet das praktisch? Oder mit anderen Worten, was ist die praktische Bedeutung von "Vererbung"?

Option 1: Der Code von Sup wird virtuell nach Sub kopiert . (wie beim Kopieren und Einfügen, jedoch ohne den in der Unterklasse visuell angezeigten kopierten Code ).

Beispiel: methodA()ist eine Methode, die ursprünglich in Sup. Sub erweitert Sup und methodA()wird (virtuell) in Sub kopiert. Jetzt hat Sub eine Methode namens methodA(). Es ist methodA()in jeder Codezeile mit Sup identisch, gehört jedoch vollständig zu Sub - und hängt nicht von Sup ab oder ist in irgendeiner Weise mit Sup verwandt.

Option 2: Der Code von Sup wird nicht in Sub kopiert . Es ist immer noch nur in der Superklasse. Auf diesen Code kann jedoch über die Unterklasse zugegriffen und von der Unterklasse verwendet werden.

Beispiel: methodA()ist eine Methode in Sup. Sub erweitert Sup, so dass jetzt methodA()über Sub wie folgt zugegriffen werden kann : subInstance.methodA(). Aber das wird tatsächlich methodA()in der Oberklasse aufgerufen. Dies bedeutet, dass methodA () im Kontext der Oberklasse arbeitet, selbst wenn sie von der Unterklasse aufgerufen wurde.

Frage: Welche der beiden Optionen funktioniert wirklich? Wenn dies nicht der Fall ist, beschreiben Sie bitte, wie diese Dinge tatsächlich funktionieren.

Aviv Cohn
quelle
Dies ist einfach selbst zu testen - schreiben Sie den Code, untersuchen Sie die Klassendateien (sogar eine Prüfsumme würde dies tun), ändern Sie die Superklasse, kompilieren Sie erneut, sehen Sie sich die Klassendateien erneut an. Lesen Sie auch Kapitel 3. Das Kompilieren für die Java Virtual Machine der JVM-Spezifikation ist hilfreich für das Verständnis (insbesondere Abschnitt 3.7).
@MichaelT " virtuell kopiert" ist das Schlüsselwort. Selbst wenn der Code buchstäblich kopiert wurde, kann dies erst nach dem Laden der Klasse geschehen.
@delnan Es wäre merkwürdig, wenn Hotspot (oder andere Laufzeitoptimierer) den Code irgendwann einbinden würde, aber dies wird zu einem Implementierungsdetail der JVM, das von JVM zu JVM unterschiedlich sein kann und daher nicht richtig beantwortet werden kann. Das Beste, was getan werden kann, ist das Betrachten des kompilierten Bytecodes (und der aufrufspezifischen Opcode-Verwendung, die beschreibt, was tatsächlich passiert)

Antworten:

13

Option 2.

Der Bytecode wird zur Laufzeit dynamisch referenziert . Deshalb treten beispielsweise LinkageErrors auf.

Angenommen, Sie kompilieren zwei Klassen:

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

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

Ändern und kompilieren Sie nun die übergeordnete Klasse, ohne die untergeordnete Klasse zu ändern oder neu zu kompilieren :

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

Führen Sie abschließend ein Programm aus, das die untergeordnete Klasse verwendet. Sie erhalten einen NoSuchMethodError :

Wird ausgelöst, wenn eine Anwendung versucht, eine bestimmte Methode einer Klasse (entweder statisch oder instanziell) aufzurufen, und diese Klasse keine Definition dieser Methode mehr hat.

Normalerweise wird dieser Fehler vom Compiler abgefangen. Dieser Fehler kann nur zur Laufzeit auftreten, wenn sich die Definition einer Klasse nicht kompatibel geändert hat.


quelle
7

Beginnen wir mit zwei einfachen Klassen:

package com.michaelt.so.supers;

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

und dann

package com.michaelt.so.supers;

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

Kompilieren von Methode A und Betrachten des Bytecodes, den man erhält:

  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

Und Sie können genau dort mit der invokespecial-Methode sehen, dass die Suche nach der Sup-Klasse methodA () durchgeführt wird.

Der aufrufspezifische Opcode hat die folgende Logik:

  • Wenn C eine Deklaration für eine Instanzmethode mit demselben Namen und Deskriptor wie die aufgelöste Methode enthält, wird diese Methode aufgerufen. Der Suchvorgang wird beendet.
  • Andernfalls wird, wenn C eine Oberklasse hat, dieselbe Suchprozedur rekursiv unter Verwendung der direkten Oberklasse von C ausgeführt. Die aufzurufende Methode ist das Ergebnis des rekursiven Aufrufs dieser Suchprozedur.
  • Andernfalls wird ein AbstractMethodError ausgelöst.

In diesem Fall gibt es in seiner Klasse keine Instanzmethode mit demselben Namen und Deskriptor, sodass das erste Aufzählungszeichen nicht ausgelöst wird. Die zweite Kugel wird jedoch - es gibt eine Superklasse und sie ruft die Methode A des Super auf.

Der Compiler integriert dies nicht und es gibt keine Kopie der Quelle von Sup in der Klasse.

Die Geschichte ist jedoch noch nicht fertig. Dies ist nur der kompilierte Code. Sobald der Code die JVM erreicht,kann sich HotSpot beteiligen.

Leider weiß ich nicht , dass viel über sie, so dass ich in dieser Angelegenheit Autorität ansprechen wird und gehen Inlining in Java , wo gesagt wird , dass HotSpot können Methoden inline (auch nicht-final - Methoden).

Wenn Sie zu den Dokumenten gehen , wird darauf hingewiesen, dass diese Informationen eingefügt werden können, wenn ein bestimmter Methodenaufruf zu einem Hot Spot wird, anstatt diese Suche jedes Mal durchzuführen. Dadurch wird der Code effektiv von Sup methodA () in Sub methodA () kopiert.

Dies erfolgt zur Laufzeit im Speicher, basierend auf dem Verhalten der Anwendung und den Optimierungen, die zur Beschleunigung der Leistung erforderlich sind.

Wie in HotSpot Internals for OpenJDK angegeben, werden "Methoden häufig inline dargestellt. Statische, private, endgültige und / oder" spezielle "Aufrufe lassen sich leicht einfügen."

Wenn Sie sich mit den Optionen für die JVM-XX:MaxInlineSize=35 befassen, finden Sie eine Option von (35 ist die Standardeinstellung), die die maximale Anzahl von Bytes darstellt, die inline gesetzt werden können. Ich werde darauf hinweisen, dass Java aus diesem Grund gerne viele kleine Methoden hat - weil sie leicht eingefügt werden können. Diese kleinen Methoden werden schneller , wenn sie mehr genannt werden , weil sie können inlined werden. Und während man mit dieser Zahl spielen und sie größer machen kann, kann dies dazu führen, dass andere Optimierungen weniger effektiv sind. (Verwandte SO-Frage: HotSpot JIT-Inlining-Strategie, die eine Reihe anderer Optionen aufzeigt, um einen Blick auf die Internals von Inlining zu werfen, die HotSpot durchführt).

Also nein - der Code wird zur Kompilierungszeit nicht eingefügt. Und ja - der Code könnte sehr gut zur Laufzeit eingefügt werden, wenn Leistungsoptimierungen dies rechtfertigen.

Und alles, was ich über HotSpot-Inlining geschrieben habe, gilt nur für HotSpot JVM, das von Oracle vertrieben wird. Wenn Sie sich die Liste der virtuellen Java-Maschinen von Wikipedia ansehen , gibt es viel mehr als nur HotSpot, und die Art und Weise, wie diese JVMs mit Inlining umgehen, kann völlig anders sein als oben beschrieben. Apache Harmony, Dalvik, ART - dort kann es anders laufen.

Gemeinschaft
quelle
0

Der Code wird nicht kopiert, es wird über Folgendes zugegriffen:

  • Die Unterklasse verweist auf ihre Methoden und die Oberklasse
  • Die Oberklasse verweist auf ihre Methoden

Compiler können optimieren, wie dies im Speicher dargestellt / ausgeführt wird, aber das ist im Grunde die Struktur

Steven A. Lowe
quelle