Résumé :
Cet article combine la réutilisation des classes Java aux orientés objetdeux grands les fonctionnalitésL'héritage et le polymorphisme sont présentés de manière exhaustive. Tout d’abord, nous avons présenté l’essence et la signification de l’héritage, et exploré les similitudes et les différences entre l’héritage, la composition et le proxy en termes de réutilisation de classes. Ensuite, nous avons introduit le polymorphisme basé sur l'héritage et présenté son mécanisme de mise en œuvre et ses applications spécifiques. De plus, pour une meilleure compréhension de l'héritage et du polymorphisme, nous fournissons une introduction complète au mot-clé final. Sur cette base, nous avons introduit la séquence de chargement et d'initialisation des classes en Java. Enfin, nous avons donné une explication détaillée de trois concepts très importants dans la conception orientée objet - surcharge , écrasement et masquage.
Points clés :
Héritage
Composition, succession, procuration
Polymorphisme
mot-clé final
Séquence de chargement et d'initialisation de classe
Surcharge, remplacement et masquage
L'héritage est un élément indispensable de tous les langages POO. En Java, utilisez Le extendsmot-clé représente la relation d'héritage. Lorsqu'une classe est créée, elle est toujours héritée. Si la classe à hériter n'est pas explicitement indiquée, elle est toujours implicitement héritée de la classe racine Object. S'il existe une relation d'héritage entre deux classes, la sous-classe héritera automatiquement des méthodes et variables de la classe parent. La sous-classe peut appeler directement les méthodes et variables de la classe parent. Il convient de souligner qu'en Java, n'autorise qu'un seul héritage , c'est-à-dire qu'une classe ne peut hériter explicitement que d'au plus une classe parent. Cependant, une classe peut être héritée par plusieurs classes, c'est-à-dire qu'une classe peut avoir plusieurs sous-classes. De plus, nous devons prêter une attention particulière aux points suivants : 1 Héritage des variables membres
Lorsqu'une sous-classe hérite d'une certaine classe, elle. peut être utilisé Variables membres dans la classe parent, mais toutes les variables membres de la classe parent ne sont pas complètement héritées. Les principes spécifiques sont les suivants :
variables membres publiques et protégées , ne peut pas hériter des variables membres privées de la classe parent, mais peut transmettre la méthode getter/setter correspondante de la classe parent pour l'accès ; Si la sous-classe et la classe parent sont dans le même package, la sous-classe peut hériter. Sinon, la sous-classe ne peut pas hériter ;
pour le parent dont la sous-classe peut hériter des variables membres de la classe,apparaît dans une sous-classe, le Un phénomène caché se produira, c'est-à-dire que la sous-classe Les variables membres de la classe parent bloqueront les variables membres de la classe parent du même nom. Si vous souhaitez accéder à une variable membre du même nom dans la classe parent d'une sous-classe, vous devez utiliser le mot-clé super pour créer une référence
.2. Héritage de la méthode membre De même, lorsqu'une sous-classe hérite d'une certaine classe, vous pouvez l'utiliser. méthodes membres dans la classe parent, mais la sous-classe n'hérite pas complètement de toutes les méthodes de la classe parent. Les principes spécifiques sont les suivants :
Les sous-classes peuvent hériter des méthodes membres publiques et protégées de la classe parent , ne peut pas hériter de la
; Pour les méthodes membres d'accès au package de la classe parent , Si la sous-classe et la classe parent sont dans le même package, la sous-classe peut hériter, sinon la sous-classe ne peut pas être hérité
Pour les méthodes membres de la classe parent dont les sous-classes peuvent hériter, Si une méthode membre du même nom apparaît dans la sous-classe, elle est appelée remplace , c'est-à-dire que les méthodes membres de la sous-classe remplaceront les méthodes membres de la classe parent portant le même nom. Si vous souhaitez accéder à une méthode membre portant le même nom dans la classe parent d'une sous-classe, vous devez utiliser le mot-clé super comme référence.
Exemple de programme :
class Person { public String gentle = "Father"; }public class Student extends Person { public String gentle = "Son"; public String print(){ return super.gentle; // 在子类中访问父类中同名成员变 } public static void main(String[] args) throws ClassNotFoundException { Student student = new Student(); System.out.println("##### " + student.gentle); Person p = student; System.out.println("***** " + p.gentle); //隐藏:编译时决定,不会发生多态 System.out.println("----- " + student.print()); System.out.println("----- " + p.print()); //Error:Person 中未定义该方法 } }/* Output: ##### Son ***** Father ----- Father *///:~
Le cache et la couverture sont différents. Caché est pour les variables membres et les méthodes statiques , tandis que Les remplacements sont par rapport aux méthodes ordinaires .
3. Initialisation et constructeur de la classe de base
On sait qu'une classe exportée est comme une classe de base avec la même interface nouvelle classe, peut-être avec quelques méthodes et champs supplémentaires. Cependant, l'héritage ne se contente pas de copier l'interface de la classe de base. Lorsque vous créez un objet de classe dérivée, l'objet contiendra un objet enfant de la classe de base. Ce sous-objet est le même que l'objet que nous créons directement à l'aide de la classe de base. La différence entre les deux est que ce dernier vient de l’extérieur, tandis que les sous-objets de la classe de base sont enveloppés à l’intérieur de l’objet de la classe dérivée.
Par conséquent, est crucial pour l'initialisation correcte des sous-objets de la classe de base, et Java fournit également des méthodes correspondantes pour garantir cela : La classe dérivée doit appelez le constructeur de classe de base dans le constructeur pour effectuer l'initialisation , et le constructeur de classe de base possède toutes les connaissances et capacités requises pour effectuer l'initialisation de la classe de base. Lorsque la classe de base contient un constructeur par défaut, Java insérera automatiquement un appel au constructeur par défaut de la classe de base dans le constructeur de la classe dérivée, car le compilateur n'a pas à considérer les paramètres à transmettre. Cependant, Si la classe parent ne contient pas de constructeur par défaut, ou si la classe dérivée souhaite appeler un constructeur de classe parent avec des paramètres, alors le mot-clé super doit être utilisé pour l'appeler explicitement dans le constructeur de la classe dérivée. class. Le constructeur de classe de base correspondant, et l'instruction appelante doivent être la première instruction du constructeur de classe dérivé.
En Java, la combinaison, l'héritage et le proxy peuvent tous implémenter du code de réutilisation.
(1) Composition (has-a)
En ajoutant la classe existante à la nouvelle classe, les objets peuvent être combinés. C'est-à-dire que la nouvelle classe est composée d'objets de classes existantes. Cette technique est généralement utilisée lorsque l'on souhaite utiliser la fonctionnalité d'une classe existante dans une nouvelle classe plutôt que son interface. En d'autres termes, intègre un objet dans une nouvelle classe afin qu'il puisse réaliser les fonctions requises, mais les utilisateurs de la nouvelle classe ne voient que l'interface définie pour la nouvelle classe plutôt que. l'interface de l'objet embarqué.
(2) Héritage (is-a)
L'héritage nous permet de créer une nouvelle classe. Autrement dit, nous prenons la forme d'une classe existante et y ajoutons un nouveau code. Habituellement, cela signifie que nous suivons un cours général et que nous le spécialisons pour un besoin particulier. Essentiellement, La composition et l'héritage permettent de placer des sous-objets dans une nouvelle classe, la composition le fait explicitement, tandis que l'héritage le fait implicitement.
(3) Proxy (un juste milieu entre héritage et composition : utiliser les fonctions des classes existantes comme composition, tout en les utilisant comme héritage Interface de classe existante)
Le proxy est un juste milieu entre l'héritage et la composition, Java ne fournit pas de support direct pour cela . Dans un proxy, nous plaçons un objet membre dans la classe en cours de construction (comme la composition), mais en même temps nous exposons l'interface/les méthodes de cet objet membre dans la nouvelle classe (comme l'héritage).
Exemple de programme :
// 控制模块public class SpaceShipControls { void up(int velocity) { } void down(int velocity) { } void left(int velocity) { } void right(int velocity) { } void forward(int velocity) { } void back(int velocity) { } void turboBoost() { } }
Le vaisseau spatial a besoin d'un module de contrôle, alors, Une façon de structurer un vaisseau spatial est d'utiliser l'héritage :
public class SpaceShip extends SpaceShipControls { private String name; public SpaceShip(String name) { this.name = name; } public String toString() { return name; } public static void main(String[] args) { SpaceShip protector = new SpaceShip("NSEA Protector"); protector.forward(100); } }
然而,SpaceShip 并不是真正的 SpaceShipControls 类型,即便你可以“告诉” SpaceShip 向前运动(forward())。更准确的说,SpaceShip 包含 SpaceShipControls ,与此同时, SpaceShipControls 的所有方法在 SpaceShip 中都暴露出来。 代理(SpaceShip 的运动行为由 SpaceShipControls 代理完成) 正好可以解决这种问题:
// SpaceShip 的行为由 SpaceShipControls 代理完成public class SpaceShipDelegation { private String name; private SpaceShipControls controls = new SpaceShipControls(); public SpaceShipDelegation(String name) { this.name = name; } // 代理方法: public void back(int velocity) { controls.back(velocity); } public void down(int velocity) { controls.down(velocity); } public void forward(int velocity) { controls.forward(velocity); } public void left(int velocity) { controls.left(velocity); } public void right(int velocity) { controls.right(velocity); } public void turboBoost() { controls.turboBoost(); } public void up(int velocity) { controls.up(velocity); } public static void main(String[] args) { SpaceShipDelegation protector = new SpaceShipDelegation("NSEA Protector"); protector.forward(100); } }
实际上,我们使用代理时可以拥有更多的控制力,因为我们可以选择只提供在成员对象中方法的某个子集。
许多编程语言都需要某种方法来向编译器告知一块数据是恒定不变的。有时,数据的恒定不变是很有用的,比如:
一个永不改变的编译时常量;
一个在运行时被初始化的值,而你不希望它被改变。
对于编译期常量这种情况,编译器可以将该常量值带入任何可能用到它的计算式中,也即是说,可以在编译时执行计算式,这减轻了一些运行时负担。在Java中,这类常量必须满足两个条件:
是基本类型,并且用final修饰;
在对这个常量进行定义的时候,必须对其进行赋值。
此外,当用final修饰对象引用时,final使其引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它指向另一个对象。然而,对象本身是可以被修改的,这同样适用于数组,因为它也是对象。
特别需要注意的是,我们不能因为某数据是final的,就认为在编译时就可以知道它的值。例如:
public class Test { final int i4 = rand.nextInt(20); }
1、空白final
Java允许生成 空白final , 即:声明final但又未给定初值的域。但无论什么情况,编译器都会确保空白final在使用前被初始化。但是,空白final在关键字final的使用上提供了更大的灵活性: 一个类中的 final域 就可以做到根据对象而有所不同,却又保持其恒定不变的特性。例如,
必须在域的定义处或者每个构造器中使用表达式对final进行赋值,这正是 final域 在使用前总是被初始化的原因所在。
2、final参数
final参数 主要应用于局部内部类和匿名内部类中,更多详细介绍请移步我的另一篇文章:Java 内部类综述。
3、final方法
final关键字作用域方法时,用于锁定方法,以防任何继承类修改它的含义。这是出于设计的考虑:想要确保在继承中使方法行为保持不变,并且不会被覆盖。
对于成员方法,只有在明确禁止覆盖时,才将方法设为final的。
4、final类
Lorsque vous définissez une classe comme finale, cela indique que vous n'avez pas l'intention d'hériter de la classe et que vous n'autorisez pas les autres à le faire. En d'autres termes, pour une raison quelconque, vous n'avez jamais besoin d'apporter de modifications à la conception de cette classe, ou pour des raisons de sécurité, vous ne voulez pas qu'elle ait genre de sous-classes.
Il est à noter que les champs de classe finale peuvent choisir d'être définitifs en fonction de la situation réelle. Qu'il soit défini ou non comme final, les mêmes règles s'appliquent aux champs définis comme finaux. Cependant, Étant donné que les classes finales interdisent l'héritage, toutes les méthodes d'une classe finale sont implicitement désignées comme finales car elles ne peuvent pas être remplacées. Vous pouvez ajouter une modification finale aux méthodes d'une classe finale, mais cela n'ajoute aucune signification.
5. finale et privée
Toutes les méthodes privées de la classe sont implicitement désignées comme finales. Étant donné que la méthode privée n'est pas accessible, elle ne peut pas être remplacée. Vous pouvez ajouter une modification finale à une méthode privée, mais cela n'ajoute aucune signification supplémentaire à la méthode.
Il est important de noter que la couverture n'apparaîtra que lorsqu'une méthode fait partie de l'interface de la classe de base . Si une méthode est privée, elle ne fait pas partie de l’interface de la classe de base, mais constitue simplement du code de programme caché dans la classe. Mais si une méthode non privée est générée avec le même nom dans la classe exportée, nous n'écrasons pas la méthode pour le moment, mais générons uniquement une nouvelle méthode. Étant donné que la méthode privée est inaccessible et peut être efficacement cachée, il n'est pas nécessaire de la considérer dans une autre situation sauf qu'elle existe en raison de la structure organisationnelle de la classe à laquelle elle appartient.
6. final et statique 🎜>
peut changerne peut modifier que les variables membres et les méthodes membres. Un champ final statique
n'occupe qu'un espace de stockage non modifiable, et L'initialisation ne peut être effectuée qu'au moment de la déclarationParce qu'elle est finale, elle n'a pas de valeur par défaut ; et elle est statique, donc une valeur lui a été attribuée lorsque la classe n'est pas instanciée, elle ne peut donc être initialisée que lorsqu'elle est déclarée. IV. Polymorphisme On sait que l'héritage permet de traiter un objet comme son propre type ou sa base. Les types sont traités afin que le même code puisse s'exécuter sur ces différents types sans aucune différence. Parmi eux, les appels de méthodes polymorphes permettent à un type de se comporter différemment des autres types similaires, à condition que ces types soient dérivés de la même classe de base. Ainsi,
Le polymorphisme par séparation Quoi faire et comment le faire, séparer l'interface et la mise en œuvre d'un autre point de vue, de manière à séparer les choses modifiées des choses inchangées
; Éliminer la relation de couplage entre les types (De même, en Java, les génériques sont également utilisés pour éliminer la relation de couplage entre les classes ou méthodes et les types utilisés).
1. Mécanisme de mise en œuvre Nous savons que la substitution de méthode incarne très bien le polymorphisme, mais lorsque vous utilisez une référence de classe de base pour appeler une méthode de substitution, quelle méthode doit être appelée correctement ? 2. Downcasting et identification du type d'exécution Étant donné que le upcasting perdra des informations de type spécifiques, nous pouvons penser qu'il devrait également être possible d'obtenir tapez les informations par downcasting. Cependant, nous savons que la conversion ascendante est sûre car la classe de base n'aura pas une interface plus grande que la classe dérivée. Par conséquent, les messages que nous envoyons via l'interface de la classe de base peuvent être acceptés, mais pour la transformation vers le bas, nous ne pouvons pas le garantir. 3. Exemples d'applications polymorphes Tout d'abord, il faut souligner que la séquence de chargement et d'initialisation de la classe est : Bloc de code statique de classe parent -> Bloc de code statique de sous-classe->Bloc de code non statique de classe parent->Classe parentConstructeur->Bloc de code non statique de sous-classe->Constructeur de sous-classe
On vous explique à travers le programme suivant : 在运行该程序时,所发生的第一件事就是试图访问 ObjectInit.main() 方法(一个static方法),于是加载器开始启动并加载 ObjectInit类 。在对其加载时,编译器注意到它有一个基类(这由关键字extends得知),于是先进行加载其基类。如果该基类还有其自身的基类,那么先加载这个父父基类,如此类推(本例中是先加载 Object类 ,再加载 SuperClass类 ,最后加载 ObjectInit类 )。接下来,根基类中的 static域 和 static代码块 会被执行,然后是下一个导出类,以此类推这种方式很重要,因为导出类的static初始化可能会依赖于基类成员能否被正确初始化。到此为止,所有的类都已加载完毕,对象就可以创建了。首先,初始化根基类所有的普通成员变量和代码块,然后执行根基类构造器以便创建一个基对象,然后是下一个导出类,依次类推,直到初始化完成。 1、重载与覆盖 (1) 定义与区别 重载:如果在一个类中定义了多个同名的方法,但它们有不同的参数(包含三方面:参数个数、参数类型和参数顺序),则称为方法的重载。其中,不能通过访问权限、返回类型和抛出异常进行重载。 总的来说,重载和覆盖是Java多态性的不同表现。前者是一个类中多态性的一种表现,后者是父类与子类之间多态性的一种表现。 (2) 实现机制 重载是一种参数多态机制,即通过方法参数的差异实现多态机制。并且,其属于一种 静态绑定机制,在编译时已经知道具体执行哪个方法。 (3) 总结 我们应该注意以下几点: final 方法不能被覆盖; 子类不能覆盖父类的private方法,否则,只是在子类中定义了一个与父类重名的全新的方法,而不会有任何覆盖效果。 其他需要注意的地方如下图所示: 2、覆盖与隐藏 (1) 定义 覆盖:指 运行时系统调用当前对象引用 运行时类型 中定义的方法 ,属于 运行期绑定。 隐藏:指运行时系统调用当前对象引用 编译时类型 中定义的方法,即 被声明或者转换为什么类型就调用对应类型中的方法或变量,属于编译期绑定。 (2) 范围 Override : uniquement pour les méthodes d'instance (3) Résumé La méthode d'instance de la sous-classe ne peut pas masquer la méthode statique de la classe parent. De même, la méthode statique de la sous-classe ne peut pas remplacer la méthode d'instance de la classe parent, sinon une erreur de compilation se produira ; 🎜> Les membres statiques et les membres d'instance peuvent être masqués par des variables membres portant le même nom dans les sous-classes. Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!
L'association d'un appel de méthode au même corps de méthode est appelée liaison . Si la liaison est effectuée avant l'exécution du programme, elle est appelée liaison anticipée . Cependant, évidemment, ce mécanisme ne peut pas résoudre le problème ci-dessus, car le compilateur ne sait pas à quel objet la référence de classe de base ci-dessus pointe au moment de la compilation. La solution est la liaison tardive (liaison dynamique/liaison d'exécution) : liaison en fonction du type spécifique de l'objet au moment de l'exécution.
En fait, en Java, à l'exception des méthodes statiques et des méthodes finales (les méthodes privées sont des méthodes finales), toutes les autres méthodes sont à liaison tardive. De cette façon, une fois qu'une méthode est déclarée finale, elle peut empêcher les autres de la remplacer, mais plus important encore : cela peut effectivement désactiver la liaison dynamique, ou en d'autres termes, indiquer au compilateur qu'elle le fait. n'a pas besoin d'être modifié. Liaison dynamique pour générer un code plus efficace pour les appels de méthode finaux.
Sur la base du mécanisme de liaison dynamique, nous pouvons écrire du code qui ne traite que des classes de base, et ces codes peuvent s'exécuter correctement pour toutes les classes exportées. En d'autres termes, Envoyez un message à un objet et laissez l'objet décider quoi faire.
Pour résoudre ce problème, il doit y avoir un moyen de garantir l'exactitude de la conversion vers le bas afin que nous ne castions pas de manière imprudente vers un mauvais type et envoyions ensuite un message que l'objet ne peut pas accepter. En Java, le mécanisme RTTI (Runtime Type Identification) peut gérer ce problème, ce qui garantit que toutes les transformations en Java seront vérifiées. Ainsi, même si nous effectuons simplement une conversion de type entre parenthèses ordinaire, elle sera toujours vérifiée lors de l'entrée dans le runtime pour s'assurer qu'il s'agit bien du type que nous voulons. Sinon, nous obtenons une exception de conversion de type : ClassCastException.
Autrement dit, initialisez d'abord les variables membres statiques et les blocs de code statiques dans la classe parent dans l'ordre dans lequel ils apparaissent dans le programme ; la sous-classe dans l'ordre dans lequel elle apparaît dans le programme. Initialisation ; deuxièmement, initialisez les variables membres ordinaires et les blocs de code de la classe parent, puis exécutez la
méthode de construction de la classe parent ; variables membres et blocs de code de la sous-classe, puis exécutez la méthode de construction de la sous-classe.
class SuperClass { private static String STR = "Super Class Static Variable"; static {
System.out.println("Super Class Static Block:" + STR);
} public SuperClass() {
System.out.println("Super Class Constructor Method");
}
{
System.out.println("Super Class Block");
}
}public class ObjectInit extends SuperClass {
private static String STR = "Class Static Variable"; static {
System.out.println("Class Static Block:" + STR);
} public ObjectInit() {
System.out.println("Constructor Method");
}
{
System.out.println("Class Block");
} public static void main(String[] args) { @SuppressWarnings("unused")
ObjectInit a = new ObjectInit();
}
}/* Output:
Super Class Static Block:Super Class Static Variable
Class Static Block:Class Static Variable
Super Class Block
Super Class Constructor Method
Class Block
Constructor Method
*///:~
六. 重载、覆盖与隐藏
覆盖:子类中定义的某个方法与其父类中某个方法具有相同的方法签名(包含相同的名称和参数列表),则称为方法的覆盖。子类对象使用这个方法时,将调用该方法在子类中的定义,对它而言,父类中该方法的定义被屏蔽了。
覆盖是一种动态绑定的多态机制。即,在父类与子类中具有相同签名的方法具有不同的具体实现,至于最终执行哪个方法 根据运行时的实际情况而定。
Masquer :Uniquement pour les méthodes statiques et les variables membres.
L'exemple de programme suivant explique les trois concepts de surcharge, de remplacement et de bon masquage :