Aujourd'hui, je vais écrire un article sur les liaisons de méthodes Java. J'étudie généralement Java et je n'en étais pas vraiment conscient, je vais donc l'organiser correctement pour le moment.
Passons en revue le casting en tant que connaissance préalable. Les casts ont des conversions de type explicites et implicites. Veuillez consulter l'URL suivante pour plus de détails. https://techacademy.jp/magazine/28486 Vous devez également connaître le timing au moment de la compilation et de l'exécution. La compilation correspond au moment où le fichier Java que vous avez écrit est converti en code octet. L'exécution est le moment où la JVM exécute réellement le code d'octet. Si vous développez dans Eclipse, vous écrivez généralement le code et il sera compilé sans autorisation, et le code sera exécuté lorsque vous appuyez sur le bouton d'exécution. Lors du développement dans un terminal, etc., compilez avec la commande javac, créez un fichier de classe et exécutez-le réellement avec la commande java. Les termes «lier» et «lier» utilisés dans l'article ont tous la même signification. (Je suis désolé, c'est déroutant ...)
Je ne connaissais même pas la signification de ce mot, mais en termes simples, c'est comme un mécanisme en Java qui connecte (lie) un appel de méthode, la signature de la méthode appelée et la partie implémentation. La signature est le nom de la méthode + l'argument, et la partie implémentation est le traitement à l'intérieur de {}. La méthode est un morceau de ces deux, mais pensez séparément pour le moment. Si vous ne comprenez pas cela, vous risquez de tomber dans des endroits inattendus (même si je ne suis pas encore ingénieur, donc je ne peux pas dire que c'est génial ...). Regardons cela concrètement.
Animal.java
public class Animal {
public void eat(double quantity) {
System.out.println("Animaux" + quantity + "porte.");
}
}
Human.java
public class Human extends Animal {
public void eat(int quantity) {
System.out.println("Les gens sont" + quantity + "porte.");
}
}
Test.java
public class Test {
public static void main(String[] args) {
Animal animal = new Human();
int quantity = 500;
animal.eat(quantity); // ?
}
}
Supposons que toutes les classes soient dans le même package. Quel sera le résultat lorsque j'exécuterai Test.java ici? Et pourquoi?
La bonne réponse est "L'animal a mangé 500,0 g." Pourquoi. Au début, je pensais que l'humain était assigné comme référence, donc le résultat devrait être "L'homme a mangé 500g." Le type int est également passé comme argument dans la classe Test. Mais en réalité, la réponse était différente. Regardons quand les liaisons de méthodes se produisent en Java pour comprendre ce qui s'est passé. Consultez le tableau ci-dessous.
Type de méthode | Liaison au moment de la compilation | Liaison au moment de l'exécution |
---|---|---|
non-méthode statique | Signature de méthode | Implémentation de la méthode |
méthode statique | Signature de méthode, implémentation de méthode |
Puisque manger est une méthode non statique cette fois, je vais expliquer sur cette base (l'idée est la même pour les méthodes statiques).
Java associe la méthode appelée à sa signature au moment de la compilation. En d'autres termes, on peut dire que le compilateur détermine à chaque fois la signature de la méthode appelée. Considérez cette règle avec le code que nous examinons actuellement. La dernière ligne de Test.java ressemble à ceci:
Test.java
animal.eat(quantity);
Tout d'abord, le compilateur examine le type de déclaration (type) de la variable animal. Le type d'animal est de type animal. Le compilateur commence alors la recherche, "Ouais, ouais. Cette variable est le type de la classe Animal. Voyons s'il y a une méthode dans cette classe qui est actuellement appelée." À ce stade, la plage de recherche comprend également des méthodes compatibles avec la méthode appelée. En fait dans la classe Animal
Animal.java
eat(double quantity)
La signature est définie. Dans l'appelant, le type int est passé à l'argument, mais la conversion de int en double est effectuée arbitrairement (sans conversion explicite), de sorte que le compilateur dit: «C'est la méthode qui est appelée maintenant. C'est une méthode compatible avec ", et elle relie l'appel de méthode de animal.eat (quantité) à la méthode eat (signature) qui prend le type double comme argument. À ce stade, le compilateur a déterminé que l'argument eat est de type double. Et il ne peut plus être modifié lors de l'exécution. Vérifions-le réellement.
Compiled from "Test.java"
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class Human
3: dup
4: invokespecial #3 // Method Human."<init>":()V
7: astore_1
8: sipush 500
11: istore_2
12: aload_1
13: iload_2
14: i2d
15: invokevirtual #4 // Method Animal.eat:(D)V
18: return
}
Il compile chaque fichier avec la commande javac du terminal, puis examine le contenu du code compilé avec javap -c Test. Voici les lignes dont vous devez être conscient.
15: invokevirtual #4 // Method Animal.eat:(D)V
Cela correspond à la partie d'appel de méthode dans la classe Test, animal.eat (quantité). (D) indique que l'argument est de type double et V indique que la valeur de retour est void. invokevirtual signifie que l'implémentation réelle est déterminée au moment de l'exécution. Le code est exécuté au Runtime selon les instructions de ce code d'octet. En d'autres termes, dans le compilateur, comme décrit ci-dessus, le contenu du processus est déterminé par la JVM au moment de l'exécution en l'associant simplement à "La méthode appelée est la méthode Animal eat".
Tout ce que vous avez à faire est d'exécuter la méthode animal.eat (quantité) selon les instructions du code d'octet. Plus tôt, j'ai dit que le compilateur examinait le type de déclaration de la variable animal. La machine virtuelle Java démarre la recherche sur l'objet affecté. En d'autres termes
Test.java
Animal animal = new Human();
Le compilateur regarde le côté gauche (Animal) de cette expression pour faire une série de liaisons, tandis que la JVM regarde d'abord l'objet sur le côté droit (Human). Puis, puisqu'il s'agit d'un objet de la classe Human, il essaie de trouver la méthode de manger: (D) V dans la classe Human. D'autre part, puisque la classe Human contient eat: (I) V (I est un type int), aucune méthode de correspondance ne peut être trouvée. Par conséquent, la machine virtuelle Java recherche la méthode correspondante dans la relation d'héritage. En d'autres termes, si vous regardez la classe parente dont la classe hérite et si elle n'est pas là, vous allez voir la classe parente, et ainsi de suite, en remontant la hiérarchie et en cherchant manger: (D) V. Dans ce cas, il y avait une méthode correspondante dans la classe Animal qui est montée d'un niveau, donc la partie implémentation est liée à la méthode appelante (animal.eat (quantité)) et le traitement est exécuté. En conséquence, le résultat était "L'animal a mangé 500,0 g."
Enfin, regardons un exemple de liaison de méthode.
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
System.out.println(list); // [1, 2, 3, 4]
list.remove(3);
System.out.println(list); // [1, 2, 3]
La méthode de suppression de type List est surchargée avec deux méthodes qui acceptent différents types d'arguments, List.remove (index int) et List.remove (Object o) (https://docs.oracle.com/javase). /jp/8/docs/api/java/util/List.html). Ici, remove a un argument de type int, donc list.remove (3) et List.remove (index int) sont liés au moment de la compilation. Et à l'exécution, le traitement (partie implémentation) dans ArrayList.remove (index int) est lié à list.remove (3). En conséquence, le 4 dans le troisième index de cette liste est supprimé et affiché comme [1, 2, 3]. Rien n'a changé jusqu'à présent. Mais qu'en est-il de l'exemple suivant?
Collection<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
System.out.println(list); // [1, 2, 3, 4]
list.remove(3);
System.out.println(list); // [1, 2, 4] ← ??
La seule chose qui a changé est que le conteneur de liste est passé d'un type de liste à un type de collection. Le résultat est affiché sous la forme [1, 2, 4]. Le numéro 3 lui-même a été supprimé, pas le troisième de l'index. En effet, Collection n'a qu'une seule méthode de suppression, Collection.remove (Object o). Le compilateur lie list.remove (3) avec la méthode remove qui prend ce type Object comme argument. Par conséquent, l'argument 3 de list.remove (3) est traité comme un objet, pas comme un index. Lorsque vous le vérifiez avec la commande javap comme auparavant, il sera affiché sous la forme Collection.remove: (Ljava / lang / Object;) Z (Z est un type booléen). Ensuite, selon les instructions du bytecode, ArrayList.remove (Object o) est maintenant exécuté par la JVM au lieu de ArrayList.remove (index int). En conséquence, [1, 2, 4] était affiché à l'écran. Si vous ne connaissiez pas la liaison de méthode, vous avez peut-être programmé en supposant que le troisième index serait normalement supprimé. Si cela se produit, cela entraînera une panne. J'ai pensé que ce serait un grand avantage de connaître ce mécanisme pour l'éviter, j'ai donc écrit cet article. Merci d'avoir lu jusqu'ici.
Recommended Posts