Pourquoi les méthodes statiques ne peuvent-elles pas être réécrites en Java? Pourquoi les méthodes statiques ne peuvent-elles pas masquer les méthodes d'instance?

Description du problème

  Pourquoi les méthodes statiques ne peuvent-elles pas être réécrites en Java? Pourquoi les méthodes statiques ne peuvent-elles pas masquer les méthodes d'instance? Etc.

Préparation préliminaire

  Comprenez d'abord la signification de la réécriture. La réécriture est la fonction de redéfinition de la méthode d'instance de la classe parent dans la sous-classe, et le type de retour, le nom de la méthode et la liste de paramètres sont cohérents, et l'appel à la méthode de réécriture dépend principalement de la valeur réelle. type. Si le type réel implémente cette méthode, appelez-la directement. Si elle n'est pas implémentée, recherchez l'implémentation de bas en haut dans la relation d'héritage. Ensuite, la question se pose à nouveau, pourquoi ne peut être remplacé que par les méthodes d'instance? Je suis tellement étourdi, ces deux problèmes échappent à la responsabilité de l'autre ici.
  Comprendre trois concepts: type statique, type réel, récepteur de méthode.

Person student= new Student();
student.work();

Le type statique est le type auquel le compilateur pense que l'objet appartient lors de la compilation. Il est principalement déterminé par le type déclaré, donc la personne mentionnée ci-dessus est le type statique. Le
type réel est déterminé par l'interpréteur en fonction de l'objet en fait pointé par la référence lors de l'exécution, le Student est donc le type réel.
Le récepteur de méthode est l'objet qui exécute cette méthode par liaison dynamique, tel que student.

Comprenez également les instructions d'appel de méthode du bytecode dans le fichier de classe compilé.
(1) invokestatic: appel méthode statique
(2) invokespecial: méthode constructeur d'instance d'appel, méthode privée.
(3) Invokevirtual: appelez toutes les méthodes virtuelles.
(4) Invokeinterface: invoquez la méthode d'interface, et un objet qui implémente cette interface sera déterminé au moment de l'exécution.
(5) Invokedynamic: résolvez d'abord dynamiquement la méthode référencée par le qualificatif de point d'appel au moment de l'exécution, puis exécutez la méthode.

Méthodes non virtuelles: méthodes qui ne peuvent pas être remplacées ou remplacées, font référence aux méthodes de construction, aux méthodes statiques, aux méthodes privées et aux méthodes modifiées finales.
Méthodes virtuelles: méthodes qui peuvent être remplacées, faisant généralement référence aux méthodes d'instance.

exemple

package com.learn.pra06;
class Demo01{
    public void method1(){
        System.out.println("This is father non-static");
    }
    public static void method2(){
        System.out.println("This is father static");
    }
}
public class Demo02 extends Demo01{
    public void method1(){
        System.out.println("This is son non-static");
    }
    public static void method2(){
        System.out.println("This is son static");
    }

    public static void main(String[] args){
        Demo01 d1= new Demo01();
        Demo02 d2= new Demo02();
        Demo01 d3= new Demo02(); //父类引用指向子类对象
        d1.method1();
        d1.method2();
        d2.method1();
        d2.method2();
        d3.method1();
        d3.method2();
    }
}

Résultat de l'opération:
Écrivez la description de l'image ici
  Il ne devrait y avoir aucun doute sur les 5 premières lignes d'un tel résultat d'opération, qui peuvent être comprises avec la pensée conventionnelle, mais la dernière, quoi? Qu'en est-il de la liaison dynamique, vous vous moquez de moi? Non, la liaison dynamique ne se produit pas ici, et la question se pose à nouveau, pourquoi la liaison dynamique ne se produit-elle pas avec les méthodes statiques? Qu'est-il arrivé à la liaison dynamique? C'est un brainstorming, pour être honnête, je suis aussi aveuglé, et le prochain peut ou peut ne pas être correct, mais c'est toujours vrai.

une analyse

Regardez d'abord le bytecode de la méthode principale ci-dessus:

 // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 19 L0
    NEW com/learn/pra06/Demo01
    DUP
    INVOKESPECIAL com/learn/pra06/Demo01.<init> ()V
    ASTORE 1
   L1
    LINENUMBER 20 L1
    NEW com/learn/pra06/Demo02
    DUP
    INVOKESPECIAL com/learn/pra06/Demo02.<init> ()V
    ASTORE 2
   L2
    LINENUMBER 21 L2
    NEW com/learn/pra06/Demo02
    DUP
    INVOKESPECIAL com/learn/pra06/Demo02.<init> ()V
    ASTORE 3
   L3
    LINENUMBER 22 L3
    ALOAD 1
    INVOKEVIRTUAL com/learn/pra06/Demo01.method1 ()V
   L4
    LINENUMBER 23 L4
    INVOKESTATIC com/learn/pra06/Demo01.method2 ()V
   L5
    LINENUMBER 24 L5
    ALOAD 2
    INVOKEVIRTUAL com/learn/pra06/Demo02.method1 ()V
   L6
    LINENUMBER 25 L6
    INVOKESTATIC com/learn/pra06/Demo02.method2 ()V
   L7
    LINENUMBER 26 L7
    ALOAD 3
    INVOKEVIRTUAL com/learn/pra06/Demo01.method1 ()V
   L8
    LINENUMBER 27 L8
    INVOKESTATIC com/learn/pra06/Demo01.method2 ()V
   L9
    LINENUMBER 28 L9
    RETURN
   L10

  Le nombre L + correspond à chaque ligne du corps de la méthode principale, on voit clairement les instructions exécutées par le code, c'est simplement de l'amour, il y a une sorte de précipitation pour se rencontrer tard.
  Demo01 d1= new Demo01();Qu'arrivera-t-il à cette instruction au moment de l'exécution? Combinez les ensembles d'instructions que nous avons préparés à apprendre au début. En regardant le bytecode ci-dessus, INVOKESPECIAL com/learn/pra06/Demo01.<init> ()Vj'ai trouvé que: Je voudrais demander au constructeur de Demo01 d'être appelé, cela ne fait aucun doute. La même chose est vraie pour L1.
  Demo01 d3= new Demo02();Bien que le type déclaré soit une classe parent, il s'agit en fait d'une sous-classe lorsqu'elle est nouvelle, et le même bytecode correspond à cela. INVOKESPECIAL com/learn/pra06/Demo02.<init> ()V
  d1.method1();Cette instruction doit être l'objet appelant sa méthode d'instance. Le bytecode illustre également ce point: INVOKEVIRTUAL com/learn/pra06/Demo01.method1 ()VINVOKEVIRTUAL est utilisé ici, ce qui signifie que la méthode virtuelle est appelée et que la référence de cette méthode est stockée dans la table des méthodes (nous en reparlerons plus tard), Seule l'instruction INVOKEVIRTUAL ira dans la table des méthodes pour trouver la référence de la méthode à appeler .
  d1.method2();Cette phrase est que l'objet appelle une méthode statique. Le bytecode est: INVOKESTATIC com/learn/pra06/Demo01.method2 ()VCette méthode consiste à appeler directement la méthode statique dans la zone des méthodes sans passer par la table des méthodes. Cela explique également que l'exécution de la méthode statique ne dépend que de la valeur statique et n'a rien à voir avec le type réel. Et comme les appels de méthode remplacés regardent le type réel, les méthodes statiques ne peuvent pas être remplacées. Les deux explications des appels de méthode de d2 sont les mêmes que d1.
  L'accent est mis sur le processus d'appel de la méthode d3. d3.method1();Bytecode: l' INVOKEVIRTUAL com/learn/pra06/Demo01.method1 ()Vinstruction INVOKEVIRTUAL est utilisée, indiquant que la méthode qui est réellement pointée sera appelée dans la table des méthodes pendant l'opération. Comme method01 peut être réécrite, le compilateur indique que method1 doit être appelée lors de l'exécution. Une référence à la méthode réelle stockée dans la table des méthodes. Comme la méthode method01 a été réécrite par Demo02, la référence de la méthode method01 de la classe parent dans la table des méthodes a été réécrite dans la méthode method01 de la sous-classe, de sorte que la méthode method01 trouvée selon l'instruction INVOKEVIRTUAL au moment de l'exécution est une sous-classe.
  Recherchez ensuite d3.method2();en regardant le bytecode:INVOKESTATIC com/learn/pra06/Demo01.method2 ()VLorsque INVOKESTATIC est utilisé, la table de méthodes n'est pas accessible, mais la méthode2 de la classe parente est directement accessible, de sorte que la méthode statique de la classe parente est appelée à l'exécution.

Le type statique (type déclaré) de l'objet est considéré comme le récepteur de la méthode au moment de la compilation. Pendant le fonctionnement, des modifications sont apportées en fonction du jeu d'instructions.

Flux d'instructions INVOKEVIRTUAL

package com.learn.pra06;
public class ClassReference {
    static class Person {
        @Override
        public String toString(){
            return "I'm a person.";
        }
        public void eat(){
            System.out.println("Person eat");
        }
        public void speak(){
            System.out.println("Person speak");
        }

    }
    static class Boy extends Person{
        @Override
        public String toString(){
            return "I'm a boy";
        }
        @Override
        public void speak(){
            System.out.println("Boy speak");
        }
        public void fight(){
            System.out.println("Boy fight");
        }
    }
    static class Girl extends Person{
        @Override
        public String toString(){
            return "I'm a girl";
        }
        @Override
        public void speak(){
            System.out.println("Girl speak");
        }
        public void sing(){
            System.out.println("Girl sing");
        }
    }
    public static void main(String[] args) {
        Person boy = new Boy();
        Person girl = new Girl();
        System.out.println(boy);
        boy.eat();
        boy.speak();
        System.out.println(girl);
        girl.eat();
        girl.speak();
    }
}

Résultat de l'exécution: étant
Écrivez la description de l'image ici
  donné que Boy et Girl n'ont pas remplacé la méthode Eat de la classe parente Person, ils appelleraient la méthode Eat de la classe parente.
Bytecode:

  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 47 L0
    NEW com/learn/pra06/ClassReference$Boy
    DUP
    INVOKESPECIAL com/learn/pra06/ClassReference$Boy.<init> ()V
    ASTORE 1
   L1
    LINENUMBER 48 L1
    NEW com/learn/pra06/ClassReference$Girl
    DUP
    INVOKESPECIAL com/learn/pra06/ClassReference$Girl.<init> ()V
    ASTORE 2
   L2
    LINENUMBER 49 L2
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 1
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L3
    LINENUMBER 50 L3
    ALOAD 1
    INVOKEVIRTUAL com/learn/pra06/ClassReference$Person.eat ()V
   L4
    LINENUMBER 51 L4
    ALOAD 1
    INVOKEVIRTUAL com/learn/pra06/ClassReference$Person.speak ()V
   L5
    LINENUMBER 53 L5
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 2
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L6
    LINENUMBER 54 L6
    ALOAD 2
    INVOKEVIRTUAL com/learn/pra06/ClassReference$Person.eat ()V
   L7
    LINENUMBER 55 L7
    ALOAD 2
    INVOKEVIRTUAL com/learn/pra06/ClassReference$Person.speak ()V
   L8
    LINENUMBER 57 L8
    RETURN
   L9

Évidemment, en L2, le compilateur écrira la référence à la méthode toString de la classe racine Object dans le fichier de classe, indiquant que le compilateur écrira la référence de méthode de l'ancêtre au lieu de ses proches parents.
L3, L4, L6 et L7 utilisent tous INVOKEVIRTUAL, quel est le processus?
Regardez d'abord le modèle de mémoire de la table de méthodes:
Écrivez la description de l'image ici
en regardant les tables de méthodes Girl et Boy, vous pouvez voir que les méthodes héritées sont organisées du début à la fin et que les références de méthode ont des index fixes dans les sous-classes, c'est-à-dire qu'elles ont tous le même décalage. Si la sous-classe réécrit une méthode de la classe parente, la référence de méthode de la table de méthodes de la sous-classe stockée à l'origine dans la classe parente deviendra la référence de la méthode réécrite. À ce stade, il faut comprendre pourquoi la méthode correcte peut être appelée en fonction du type d'objet La clé de la méthode se trouve dans la table des méthodes . Prenez le
suivant comme girl.speakun exemple, jetez un oeil à l'écoulement d'instructions invokevirtual
Écrivez la description de l'image ici
schéma d'explication:
1. Tout d' abord, invokevirtual com / apprendre / pra06 / ClassReferencePerson.speak () V, selon com / apprendre / pra06 / ClassReference

Person.speak () V trouve l'offset de la méthode dans le pool de constantes
2. Vérifiez la table de méthode de Person pour obtenir l'offset de la méthode speak dans la table de méthode (en supposant qu'il est de 15), afin que vous puissiez obtenir un référence à la méthode.
3. Selon cela, jugez que la référence fait référence à l'instance Girl
4. Ensuite, allez dans la table de méthode de l'instance Girl, et trouvez la référence de méthode dans la table de méthode en fonction du décalage ci-dessus, car la valeur de la référence de méthode est chargé dans la classe selon que la réécriture de la méthode a déterminé la référence de méthode correcte, nous pouvons donc appeler directement la méthode ici.

Pourquoi les méthodes statiques ne peuvent-elles pas masquer les méthodes d'instance?

L'appel de la méthode statique est lié statiquement dans le compilateur, et l'appel de la méthode d'instance est lié dynamiquement au moment de l'exécution. Les deux méthodes d'appel sont différentes, donc une seule des deux peut exister, sinon il y aura ambiguïté.!

Pourquoi les méthodes statiques peuvent-elles masquer les méthodes statiques?

La méthode d'appel étant la même, elle ne provoquera pas d'ambiguïté comme ci-dessus. Bien que la classe parente et la sous-classe définissent la même fonction, le compilateur activera la référence de la méthode statique correspondante en fonction du type statique de l'objet, provoquant la illusion de réécriture. Pas une réécriture!

Pour résumer

Le processus global est le suivant: le compilateur compile la classe dans un fichier de classe, dans lequel la méthode écrira la référence de méthode correspondante dans la classe en fonction du type statique. Au moment de l'exécution, la machine virtuelle Java trouvera l'écart de la méthode dans le pool de constantes selon la référence de méthode pointée par INVOKEVIRTUAL Shift, puis recherchez l'objet effectivement pointé par le type de référence en fonction de celui-ci, accédez à la table de méthode de ce type d'objet, recherchez l'emplacement où la référence de méthode cible est stockée en fonction du décalage, retirer la référence, appeler la méthode effectivement pointée par la référence et compléter le polymorphisme

Je suppose que tu aimes

Origine blog.csdn.net/qq_32907195/article/details/112294312
conseillé
Classement