Héritage: le code de la superclasse est-il virtuellement * copié * dans la sous-classe, ou est-il * référencé par la sous-classe *?

10

La classe Subest une sous-classe de classe Sup. Qu'est-ce que cela signifie pratiquement? Ou en d'autres termes, quelle est la signification pratique de "l'héritage"?

Option 1: le code de Sup est virtuellement copié dans Sub. (comme dans «copier-coller», mais sans le code copié visible visuellement dans la sous-classe).

Exemple: methodA()est une méthode originellement dans Sup. Sub étend Sup, il methodA()est donc (virtuellement) copié-collé sur Sub. Maintenant, Sub a une méthode nommée methodA(). Il est identique à Sup methodA()dans chaque ligne de code, mais appartient entièrement à Sub - et ne dépend pas de Sup ou n'est lié à Sup en aucune façon.

Option 2: le code de Sup n'est pas réellement copié dans Sub. Ce n'est toujours que dans la superclasse. Mais ce code est accessible via la sous-classe et peut être utilisé par la sous-classe.

Exemple: methodA()est une méthode dans Sup. Sous étend Sup, maintenant methodA()accessible via Sub comme ceci: subInstance.methodA(). Mais cela va en fait être invoqué methodA()dans la superclasse. Ce qui signifie que methodA () fonctionnera dans le contexte de la superclasse, même si elle a été appelée par la sous-classe.

Question: Laquelle des deux options est vraiment la façon dont les choses fonctionnent? Si aucun d'entre eux ne l'est, veuillez décrire comment ces choses fonctionnent réellement.

Aviv Cohn
la source
C'est facile à tester par vous-même - écrivez le code, examinez les fichiers de classe (même une somme de contrôle le ferait), modifiez la super classe, compilez à nouveau, regardez à nouveau les fichiers de classe. La lecture du chapitre 3. Compilation pour la machine virtuelle Java de la spécification JVM peut également être utile pour comprendre (en particulier la section 3.7).
@MichaelT " virtuellement copié" est le mot clé. De plus, même si le code était littéralement copié, cela ne pourrait se produire qu'après le chargement de la classe.
@delnan, il serait curieux que Hotspot (ou d'autres optimiseurs d'exécution) intègrent le code à un moment donné, mais cela devient un détail d'implémentation de la JVM qui peut différer d'une JVM à une autre et ne peut donc pas être répondu correctement. Le mieux qui puisse être fait est de regarder le bytecode compilé (et l' utilisation de l' opcode invokespecial qui décrit ce qui se passe réellement)

Réponses:

13

Option 2.

Le bytecode est référencé dynamiquement lors de l'exécution: c'est pourquoi, par exemple, LinkageErrors se produit.

Par exemple, supposons que vous compiliez deux classes:

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

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

Modifiez et recompilez maintenant la classe parent sans modifier ni recompiler la classe enfant :

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

Enfin, exécutez un programme qui utilise la classe enfant. Vous recevrez une NoSuchMethodError :

Lancé si une application tente d'appeler une méthode spécifiée d'une classe (statique ou instance) et que cette classe n'a plus de définition de cette méthode.

Normalement, cette erreur est détectée par le compilateur; cette erreur ne peut se produire au moment de l'exécution que si la définition d'une classe a changé de manière incompatibl.


la source
7

Commençons par deux classes simples:

package com.michaelt.so.supers;

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

et alors

package com.michaelt.so.supers;

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

En compilant la méthode A et en regardant le code d'octet, on obtient:

  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

Et vous pouvez voir juste là avec la méthode invokespecial qu'il fait la recherche contre la méthode de classe SupA ().

L' opcode invokespecial a la logique suivante:

  • Si C contient une déclaration pour une méthode d'instance avec le même nom et descripteur que la méthode résolue, alors cette méthode sera invoquée. La procédure de recherche se termine.
  • Sinon, si C a une superclasse, cette même procédure de recherche est effectuée récursivement en utilisant la superclasse directe de C. La méthode à invoquer est le résultat de l'invocation récursive de cette procédure de recherche.
  • Sinon, une AbstractMethodError est déclenchée.

Dans ce cas, il n'y a pas de méthode d'instance avec le même nom et le même descripteur dans sa classe, donc la première puce ne va pas se déclencher. La deuxième puce le fera cependant - il y a une superclasse et elle invoque la méthode A du super.

Le compilateur n'inline pas cela et il n'y a pas de copie de la source de Sup dans la classe.

Mais l'histoire n'est pas encore terminée. Ce n'est que lecode compilé . Une fois que le code atteint la JVM, HotSpot peut s'impliquer.

Malheureusement, je ne sais pas grand-chose à ce sujet, je vais donc faire appel à l'autorité à ce sujet et aller à Inlining à Java où il est dit que HotSpot peut incorporer des méthodes (même des méthodes non définitives).

En allant dans la documentation, il est à noter que si un appel de méthode particulier devient un point chaud au lieu de faire cette recherche à chaque fois, ces informations peuvent être intégrées - en copiant efficacement le code de Sup methodA () dans Sub methodA ().

Cela se fait au moment de l'exécution, en mémoire, en fonction du comportement de l'application et des optimisations nécessaires pour accélérer les performances.

Comme indiqué dans HotSpot Internals for OpenJDK "Les méthodes sont souvent intégrées. Les invocations statiques, privées, finales et / ou" spéciales "sont faciles à intégrer."

Si vous creusez dans les options de la JVM, vous trouverez une option -XX:MaxInlineSize=35(35 étant la valeur par défaut) qui est le nombre maximal d'octets pouvant être insérés. Je soulignerai que c'est pourquoi Java aime avoir beaucoup de petites méthodes - car elles peuvent être facilement intégrées. Ces petites méthodes deviennent plus rapides lorsqu'elles sont appelées davantage car elles peuvent être intégrées. Et bien que l'on puisse jouer avec ce nombre et l'agrandir, cela peut rendre d'autres optimisations moins efficaces. (question SO connexe: stratégie d'inclusion de HotSpot JIT qui souligne un certain nombre d'autres options pour jeter un œil aux aspects internes de l'inclusion que HotSpot fait).

Donc, non - le code n'est pas inséré au moment de la compilation. Et, oui - le code pourrait très bien être intégré à l'exécution si les optimisations de performances le justifient.

Et tout ce que j'ai écrit sur l'incrustation HotSpot s'applique uniquement à la machine virtuelle Java HotSpot distribuée par Oracle. Si vous regardez la liste des machines virtuelles Java de wikipedia, il y a bien plus que HotSpot et la façon dont ces JVM gèrent l'inline peut être complètement différente de ce que j'ai décrit ci-dessus. Apache Harmony, Dalvik, ART - les choses peuvent fonctionner différemment là-bas.

Communauté
la source
0

le code n'est pas copié, il est accessible par référence:

  • la sous-classe référence ses méthodes et la super-classe
  • la superclasse référence ses méthodes

les compilateurs peuvent optimiser la façon dont cela est représenté / exécuté en mémoire, mais c'est essentiellement la structure

Steven A. Lowe
la source