[Java SE] Héritage et polymorphisme

Table des matières

【1】Héritage

【1.1】Pourquoi l'héritage

【1.2】Concept d'héritage

【1.3】Syntaxe héritée

【1.4】Accès aux membres de la classe parent

【1.4.1】Accès aux variables membres de la classe parent dans une sous-classe

【1.4.2】Accès aux variables membres de la classe parent dans une sous-classe

【1.5】super mot-clé

【1.6】Méthode de construction de sous-classe

【1.7】super et ça

【1.8】Parlons encore de l'initialisation

【1.9】mot-clé protégé

【1.10】Méthode d'héritage

【1.11】mot-clé final

【1.12】Héritage et combinaison

【2】Polymorphisme

【2.1】Le concept de polymorphisme

【2.2】Conditions de mise en œuvre polymorphes

【2.3】Réécriture

【2.4】Transfert vers le haut et transformation vers le bas

【2.4.1】Transformation vers le haut

【2.4.2】Transformation vers le bas

【2.5】Avantages et inconvénients du polymorphisme

【2.6】Évitez d'appeler des méthodes remplacées dans le constructeur

Héritage et polymorphisme

【1】Héritage

【1.1】Pourquoi l'héritage

        Java utilise des classes pour décrire des entités dans le monde réel. L'objet produit après instanciation d'une classe peut être utilisé pour représenter des entités dans le monde réel. Cependant, le monde réel est complexe et il peut y avoir des corrélations entre les choses. Lors de la conception du programme , Oui, il faut y réfléchir.

[Par exemple] : Les chiens et les chats, ce sont tous deux des espèces animales.

 

En utilisant le langage Java pour décrire, vous concevrez :

/** - 动物狗类
 */
class Dog {
    public String _name;
    public int _age;
    public float _weight;

    public void eat() {
        System.out.println(_name + "正在吃饭");
    }

    public void sleep() {
        System.out.println(_name + "正在睡觉");
    }

    public void Bark() {
        System.out.println(_name + "汪汪汪~~~");
    }
}

/** - 动物猫类
 */
class Cat {
    public String _name;
    public int _age;
    public float _weight;

    public void eat() {
        System.out.println(_name + "正在吃饭");
    }

    public void sleep() {
        System.out.println(_name + "正在睡觉");
    }

    public void Bark() {
        System.out.println(_name + "喵喵喵~~~");
    }
}

En observant le code ci-dessus, vous constaterez qu'il y a beaucoup de duplications dans les classes de chat et de chien, comme indiqué ci-dessous :

 

        Ces points communs peuvent-ils être extraits ? Le concept d'héritage est proposé dans la pensée orientée objet, spécialement utilisé pour extraire des caractéristiques communes et réaliser la réutilisation du code .

【1.2】Concept d'héritage

        Mécanisme d'héritage : C'est le moyen le plus important de la programmation orientée objet pour rendre le code réutilisable. Il permet aux programmeurs d'étendre et d' ajouter de nouvelles fonctions tout en conservant les caractéristiques de la classe d'origine . Cela génère une nouvelle classe, appelée dérivation. genre. L'héritage présente la structure hiérarchique de la programmation orientée objet et reflète le processus cognitif du simple au complexe. Le principal problème résolu par l'héritage est : l'extraction des fonctionnalités communes et la réalisation de la réutilisation du code . Par exemple : les chiens et les chats sont tous deux des animaux, on peut alors extraire le contenu commun puis utiliser l'idée d'héritage pour réaliser le partage.

         

        Dans l'illustration ci-dessus, Dog et Cat héritent tous deux de la classe Animal , où : la classe Animal est appelée classe parent/classe de base ou super classe, et Dog et Cat peuvent être appelés les sous-classes/classes dérivées d'Animal. Après l'héritage, les sous-classes peuvent être dupliquées. En utilisant les membres de la classe parent, la sous-classe n'a besoin de se soucier que de ses propres membres nouvellement ajoutés lors de son implémentation. Du concept d'héritage, nous pouvons voir que le rôle le plus important de l'héritage est de parvenir à la réutilisation du code et d'obtenir le polymorphisme (discuté plus tard).

【1.3】Syntaxe héritée

En Java, si vous souhaitez exprimer la relation d'héritage entre les classes, vous devez utiliser le mot-clé extends , comme suit :

修饰符 class 子类 extends 父类 {
	// ...
}

[Repensé pour les chiens et les chats]

 


/** - 动物类(父类)
 * 
 */
class Animal {
    public String _name;
    public int _age;
    public float _weight;
    public void eat() {
        System.out.println(_name + "正在吃饭");
    }

    public void sleep() {
        System.out.println(_name + "正在睡觉");
    }

    public void bark() {
        System.out.println(_name + "汪汪汪~~~");
    }
}

/** - 动物狗类(子类)
 */
class Dog extends Animal {
    public void print() {
        System.out.println("我是" + _name + "新的功能");
    }
}

/** - 动物猫类(子类)
 */
class Cat extends Animal {
    public void print() {
        System.out.println("我是" + _name + "新的功能");
    }
}

【transfert】

public static void main(String[] args) {
    Dog dog = new Dog();
    dog._name = "旺财";
    dog._age = 2;
    dog._weight = 12;
    dog.eat();
    dog.sleep();
    dog.bark();
    dog.print();
    
    Cat cat = new Cat();
    cat._name = "小六";
    cat._age = 3;
    cat._weight = 20;
    cat.eat();
    cat.sleep();
    cat.bark();
    cat.print();
}

【Avis】

  1. La sous-classe héritera des variables membres ou des méthodes membres de la classe parent de la sous-classe.

  2. Une fois que la sous-classe a hérité de la classe parent, elle doit ajouter ses propres membres uniques pour refléter la différence par rapport à la classe de base, sinon il n'est pas nécessaire d'hériter.

【1.4】Accès aux membres de la classe parent

【1.4.1】Accès aux variables membres de la classe parent dans une sous-classe

  1. Il n'existe aucune variable membre portant le même nom dans les sous-classes et les classes parentes.

class Base {
    int a;
    int b;
}

public class Derived extends Base {
    int c;
    public void method() {
        a = 10;     // 访问父类中的a
        b = 20;     // 访问父类中的b
        c = 30;     // 访问子类中的c
    }
}
  1. Les variables membres de la sous-classe et de la classe parent portent le même nom.

class Base {
    int a;
    int b;
}

public class Derived extends Base {
    int a;  // 与父类的成员a同名,但类型相同
    char b;  // 与父类的成员b同名,但类型不同

    int c;
    public void method() {
        a = 101;
        b = 102;
        c = 103;

        System.out.println(a);    // 访问父类继承的a,还是子类自己新增的a? (就近原则访问子类)
        System.out.println(b);    // 访问父类继承的b,还是子类自己新增的b?  (就近原则访问子类)
        System.out.println(c);    // 子类没有c,访问的肯定是从父类继承下来的c
        // System.out.println(d); // d = 103; // 编译失败,因为父类和子类都没有定义成员变量b
    }

    public static void main(String[] args) {
        Derived derived = new Derived();
        derived.method();
    }
}

Lors de l'accès aux membres dans une méthode de sous-classe ou via un objet de sous-classe :

  • Si la variable membre à laquelle accéder existe dans une sous-classe, accédez d’abord à votre propre variable membre.

  • Si la variable membre consultée n'existe pas dans la sous-classe,elle sera accessible en héritant de la classe parent. Si la classe parent n'est pas définie,une erreur de compilation sera signalée.

  • Si la variable membre à laquelle vous accédez porte le même nom qu’une variable membre de la classe parent, la vôtre sera accessible en premier.

L'accès aux variables membres suit le principe de proximité. Si vous en avez une, vous êtes prioritaire. Dans le cas contraire, vous la retrouverez dans la classe parent. 

【1.4.2】Accès aux variables membres de la classe parent dans une sous-classe

  1. Les noms des méthodes membres sont différents

class Base {
    public void methodA(){
        System.out.println("Base中的methodA()");
    }
}
public class Derived extends Base{
    public void methodB(){
        System.out.println("Derived中的methodB()方法");
    }
    public void methodC(){
        methodB(); // 访问子类自己的methodB()
        methodA(); // 访问父类继承的methodA()
        // methodD(); // 编译失败,在整个继承体系中没有发现方法methodD()
    }
}

[Résumé] Lorsqu'une méthode membre n'a pas le même nom, lorsque vous accédez à la méthode dans une méthode de sous-classe ou via un objet de sous-classe, vous accéderez d'abord à votre propre méthode. Si vous ne la trouvez pas vous-même, vous la chercherez dans la classe parent. S’il n’existe pas dans la classe parent, une erreur sera signalée.

  1. Les méthodes membres ont le même nom

public class Base {
    public void methodA(){
        System.out.println("Base中的methodA()");
    }
    public void methodB(){
        System.out.println("Base中的methodB()");
    }
}

public class Derived extends Base{
    public void methodA(int a) {
        System.out.println("Derived中的method(int)方法");
    }
    public void methodB(){
        System.out.println("Derived中的methodB()方法");
    }
    
    public void methodC(){
        methodA(); 		// 没有传参,访问父类中的methodA()
        methodA(20); 	// 传递int参数,访问子类中的methodA(int)
        methodB(); 		// 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
    }
}

【illustrer】

  • Lors de l'accès à des méthodes portant des noms différents dans la classe parent et dans la sous-classe via l'objet de sous-classe, la méthode est d'abord recherchée dans la sous-classe et accessible si elle est trouvée. Sinon, la méthode est recherchée dans la classe parent et accessible si elle est trouvée. Sinon, une compilation une erreur est signalée.

  • Lors de l'accès à la méthode portant le même nom de la classe parent et de la sous-classe via l'objet de classe dérivée, si les listes de paramètres de la méthode portant le même nom de la classe parent et de la sous-classe sont différentes (surchargées), sélectionnez la méthode appropriée pour accès en fonction des paramètres passés par la méthode appelante, sinon, une erreur sera signalée.

[Question] S'il y a des membres dans la sous-classe qui sont les mêmes que ceux de la classe parent, comment accéder aux membres portant le même nom dans la classe parent de la sous-classe ?

[Réponse] Vous devez utiliser le super mot-clé .

【1.5】super mot-clé

        En raison de mauvaises exigences de conception ou de scénario, il peut y avoir des membres portant le même nom dans la sous-classe et la classe parent. Que dois-je faire si je souhaite accéder à un membre de la classe parent portant le même nom dans une méthode de sous-classe ?

        L'accès direct n'est pas possible.Java fournit le mot - clé super, qui est principalement utilisé pour accéder aux membres de la classe parent dans les méthodes de sous-classe .

/** - 父类
 *
 */
class Base {
    int a;
    int b;

    public void methodA() {
        System.out.println("Base::methodA()");
    }

    public void methodB() {
        System.out.println("Base::methodB()");
    }
}

/** - 子类
 *  - 继承Base类
 */
class Derived extends Base {
    int a;      // 与Base父类中成员变量同名切类型相同。
    char b;     // 与Base父类中成员变量同名切类型不相同。

    // 与Base父类中的methodA函数构成重载。
    public void methodA(int a) {
        System.out.println("Derived::methodA(int a)");
    }

    // 与Base父类中的methodA函数重写(即原型一致,重写后续详细介绍)
    public void methodB() {
        System.out.println("Derived::methodB()");
    }

    public void methodC() {
        // 对于同名的成员变量,直接访问时,访问的都是子类的
        a = 100;    // 相当于this.a
        b = 101;    // 相当于this.b
        // this是当前对象的引用,如果在子类中找不到a | b会继续到父类中找

        // 注意:如果像指定访问父类对象需要使用super关键字来修饰
        super.a = 100;
        super.b = 101;
        // super是只会在父类继承下来中找


        // 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
        methodA(10);    // 传参调用子类的的methodA方法
        methodA();         // 没有传参调用父类的methodA方法

        // methodB是父类与子类重名。
        methodB();  // 直接调用会调用子类的methodB
        super.methodB();  // 增加super关键字,会调用父类中的methodB
    }
}

Dans une méthode de sous-classe, si vous souhaitez accéder explicitement aux membres de la classe parent, vous pouvez utiliser le mot-clé super.

Remarques

  1. Ne peut être utilisé que dans des méthodes non statiques.

  2. Dans la méthode de sous - classe, accédez aux variables et méthodes membres de la classe parent .

【1.6】Méthode de construction de sous-classe

        Père et fils, père et fils, il y a d'abord le père puis le fils, c'est-à-dire : lors de la construction d'un objet de sous-classe, vous devez d'abord appeler le constructeur de classe de base, puis exécuter le constructeur de sous-classe.

/** - 动物类(父类)
 *
 */
class Animal {
    public Animal(String name, int age, float weight) {
        this._name = name;
        this._age = age;
        this._weight = weight;
    }

    public String _name;
    public int _age;
    public float _weight;
    public void eat() {
        System.out.println(_name + "正在吃饭");
    }

    public void sleep() {
        System.out.println(_name + "正在睡觉");
    }

    public void bark() {
        System.out.println(_name + "汪汪汪~~~");
    }
}

/** - 动物狗类(子类)
 */
class Dog extends Animal {
    public Dog(String name, int age, float weight) {
        super(name, age, weight);   // 先构造父类的构造方法
        // .. 执行子类的构造方法
    }
    public void print() {
        System.out.println("我是" + _name + "新的功能");
    }
}

/** - 动物猫类(子类)
 */
class Cat extends Animal {
    public Cat(String name, int age, float weight) {
        super(name, age, weight);   // 先构造父类的构造方法
        // .. 执行子类的构造方法
    }
    
    public void print() {
        System.out.println("我是" + _name + "新的功能");
    }
}

        Dans la méthode de construction de sous-classe, aucun code sur la construction de la classe de base n'est écrit, mais lors de la construction de l'objet de sous-classe, la méthode de construction de la classe de base est exécutée en premier, puis la méthode de construction de la sous-classe est exécutée, car : les membres de l'objet de sous-classe sont Il se compose de deux parties, la partie héritée de la classe de base et la partie nouvellement ajoutée par la sous-classe. Le père et le fils doivent d'abord avoir un père, puis un fils, donc lors de la construction d'un objet de sous-classe, vous devez d'abord appeler la méthode de construction de la classe de base pour terminer la construction des membres hérités de la classe de base, puis appeler la propre méthode de construction de la sous-classe. pour terminer la construction des membres hérités de la classe de base.Les membres nouvellement ajoutés de la sous-classe sont complètement initialisés. 【Avis】

  1. Si la classe parent définit explicitement un constructeur sans paramètre ou par défaut, il existe un appel super() implicite par défaut dans la première ligne du constructeur de sous-classe, c'est-à-dire que le constructeur de la classe de base est appelé.

  2. Si le constructeur de la classe parent a des paramètres, l'utilisateur doit définir explicitement le constructeur de la sous-classe et sélectionner le constructeur de la classe parent approprié à appeler dans le constructeur de la sous-classe, sinon la compilation échouera.

  3. Dans le constructeur de sous-classe, lorsque super(...) appelle le constructeur de classe parent, il doit s'agir de la première instruction du constructeur de sous-classe.

  4. super(...) ne peut apparaître qu'une seule fois dans le constructeur de sous-classe et ne peut pas apparaître en même temps que celui-ci

【1.7】super et ça

        Super et this peuvent être utilisés pour accéder aux méthodes membres : les variables membres et l'appel d'autres fonctions membres peuvent tous deux être utilisés comme première instruction du constructeur. Alors, quelle est la différence entre elles ?

Points similaires

  1. Ce sont tous des mots-clés en Java.

  2. Il ne peut être utilisé que dans les méthodes non statiques d’une classe pour accéder aux méthodes et champs membres non statiques.

  3. Lorsqu'elle est appelée dans un constructeur, elle doit être la première instruction du constructeur et ne peut pas exister en même temps.

Différences

  1. c'est une référence à l'objet actuel,qui est l'objet qui appelle la méthode d'instance.Super équivaut à une référence à certains membres de l'objet de sous-classe hérité de la classe parent.

 

  1. Dans les méthodes membres non statiques, this est utilisé pour accéder aux méthodes et propriétés de cette classe, et super est utilisé pour accéder aux méthodes et propriétés héritées de la classe parent.

  2. Dans la méthode constructeur : this(...) permet d'appeler la méthode constructeur de cette classe, et super(...) permet d'appeler la méthode constructeur de la classe parent. Les deux appels ne peuvent pas apparaître dans la méthode constructeur en même temps.

  3. Il doit y avoir un appel à super(...) dans le constructeur. Le compilateur l'augmentera si l'utilisateur ne l'écrit pas, mais ceci(...) n'augmentera pas si l'utilisateur ne l'écrit pas.

【1.8】Parlons encore de l'initialisation

        Nous souvenons-nous des blocs de code dont nous avons parlé plus tôt ? Passons brièvement en revue plusieurs blocs de code importants : les blocs de code d'instance et les blocs de code statiques. Ordre d'exécution lorsqu'il n'y a pas de relation successorale.

class Person {
    // 属性
    public String _name;
    public int _age;
    // 构造函数
    public Person(String name, int age) {
        _name = name;
        _age = age;
        System.out.println("Person::构造代码块");
    }
    // 成员方法
    static {
        System.out.println("Person::静态代码块");
    }

    {
        System.out.println("Person::实例代码块");
    }
}
public class Main {
    public static void main(String[] args) {
        Person perosn = new Person("ShaXiang", 18);
    }
}

【Résultats du】

Person::静态代码块
Person::实例代码块
Person::构造代码块
  1. Le bloc de code statique est exécuté une première et une seule fois, lors de la phase de chargement de la classe.

  2. Lorsqu'un objet est créé, le bloc de code d'instance sera exécuté. Une fois le bloc de code d'instance exécuté, la méthode de constructeur finale sera exécutée.

[ Ordre d'exécution basé sur le lien de succession ]

class Person {
    // 属性
    public String _name;
    public int _age;
    // 构造函数
    public Person(String name, int age) {
        _name = name;
        _age = age;
        System.out.println("Person::构造代码块");
    }
    // 成员方法
    static {
        System.out.println("Person::静态代码块");
    }

    {
        System.out.println("Person::实例代码块");
    }
}

class Student extends Person {
    public Student(String name, int age) {
        super(name, age);
        System.out.println("Student::构造代码块");
    }

    static {
        System.out.println("Student::静态代码块");
    }

    {
        System.out.println("Student::实例代码块");
    }
}

public class Main {
    public static void main(String[] args) {
        Student student = new Student("ShaXiang", 18);
    }
}

【Résultats du】

Person::静态代码块
Student::静态代码块
Person::实例代码块
Person::构造代码块
Student::实例代码块
Student::构造代码块

En analysant les résultats de l'exécution, les conclusions suivantes sont tirées : 1. Le bloc de code statique de la classe parent est exécuté avant le bloc de code statique de la sous-classe et est exécuté le plus tôt possible. 2. Le bloc de code de l'instance de classe parent et la méthode de construction de classe parent sont exécutés immédiatement. 3. Le bloc de code d'instance et la méthode de construction de sous-classe de la sous-classe sont exécutés immédiatement. 4. Lorsque l'objet de sous-classe est instancié pour la deuxième fois, les blocs de code statiques de la classe parent et de la sous-classe ne seront plus exécutés.

【1.9】mot-clé protégé

Dans le chapitre sur les classes et les objets, afin de réaliser la fonctionnalité d'encapsulation, Java introduit des qualificatifs d'accès, qui limitent principalement si la classe ou les membres de la classe sont accessibles en dehors de la classe ou dans d'autres packages.

 

Alors, quelle est la visibilité des membres ayant des droits d’accès différents dans la classe parent dans la sous-classe ?

// 为了掩饰基类中不同访问权限在子类中的可见性,为了简单类B中就不设置成员方法了。
// 此B类在Package01中。
public class B {
    private int a;
    protected int b;
    public int c;
    int d;
}

// D类与B类在同一个Package包中。
// 此D类在Package01中。
public class D extends B {
    public void method() {
        // super.a = 10;    // 编译报错,父类private成员在相同包子类中不可见。
        super.b = 20;       // 父类中protected成员在相同包子类中可以直接访问。
        super.c = 30;       // 父类中public成员在相同包子类中可以直接访问。
        super.d = 40;       // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问。
    }
}

// C类与B类不在同一个Package包中。
// 此C类在Package02中。
public class C extends B {
    public void method() {
        // super.a = 10;    // 编译报错,父类private成员在相同包子类中不可见。
        super.b = 20;       // 父类中protected修饰的成员在不同包子类中可以直接访问。
        super.c = 30;       // 父类中public成员在相同包子类中可以直接访问。
        // super.d = 40;    // 父类中默认访问权限修饰的成员在不同包子类中不能直接访问。
    }
}

// TestC类与C类在同一个Package包中。
// 此TestC类在Package02中。
public class TestC {
    public static void main(String[] args) {
        C c = new C();
        c.method();

        // System.out.println(c.a);;    // 编译报错,父类中private成员在不同包其他类中不可见。
        // System.out.println(c.b);;    // 父类中protected成员在不同包其他类中不能直接访问。
        System.out.println(c.c);;       // 父类中public成员在不同包其他类中可以直接访问。
        // System.out.println(c.d);;    // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问。
    }
}

[Note] Bien que les variables membres privées de la classe parent ne soient pas directement accessibles dans la sous-classe, elles sont également héritées dans la sous-classe.

        Quand dois-je utiliser lequel ? Nous espérons que la classe doit être aussi "encapsulée" que possible, c'est-à-dire masquer les détails d'implémentation internes et exposer uniquement les informations nécessaires à l'appelant de la classe. Par conséquent, nous devrions utiliser la comparaison autant que possible. possible lors de son utilisation. Autorisations d'accès strictes. Par exemple, si une méthode peut être privée, essayez de ne pas l'utiliser publique. De plus, il existe une méthode simple et grossière : définir tous les champs comme privés et toutes les méthodes comme publiques. Cependant, cette méthode est-elle un abus des droits d'accès ? J'espère toujours que les étudiants pourront réfléchir attentivement lorsqu'ils écrivent du code. , "Qui" devrait utiliser les méthodes de champ fournies par cette classe (qu'elles soient utilisées en interne par la classe elle-même, par les appelants de la classe ou par les sous-classes).

【1.10】Méthode d'héritage

 

Cependant, seules les méthodes d'héritage suivantes sont prises en charge en Java :

 

[Note] L'héritage multiple n'est pas pris en charge en Java.

        Gardez toujours à l'esprit que les cours que nous écrivons sont des abstractions de choses réelles. Les projets que nous rencontrons réellement dans l'entreprise sont souvent plus complexes en affaires et peuvent impliquer une série de concepts complexes, qui nécessitent tous l'utilisation de code pour les représenter, donc nous avons vraiment de nombreuses classes écrites dans le projet, et les relations entre les classes seront également plus complexes.

        Mais même ainsi, nous ne voulons pas que la hiérarchie d'héritage entre les classes soit trop compliquée. En général, nous ne voulons pas plus de trois niveaux de relations d'héritage . S'il y a trop de niveaux d'héritage, nous devons envisager de refactoriser le Code.

        Si vous souhaitez restreindre l'héritage syntaxiquement, vous pouvez utiliser le mot-clé final.

【1.11】mot-clé final

La clé finale peut être utilisée pour modifier les variables, les méthodes membres et les classes.

  1. Modifie une variable ou un champ pour représenter une constante (c'est-à-dire qu'il ne peut pas être modifié).

final int a = 10;
a = 20; // 编译出错
  1. Classe modifiée : indique que cette classe ne peut pas être héritée.

final public class Animal {
...
}
public class Bird extends Animal {
...
} 
// 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继

 

La classe String que nous utilisons habituellement est modifiée avec final et ne peut pas être héritée.

  1. Méthode modifiée : indique que la méthode ne peut pas être remplacée (décrit plus tard)

【1.12】Héritage et combinaison

Semblable à l'héritage, la composition est également un moyen d'exprimer la relation entre les classes et peut également obtenir l'effet de réutilisation du code. La composition n'implique pas de syntaxe particulière (mots clés tels que extends), utilisant simplement une instance d'une classe comme champ d'une autre classe.

L'héritage représente une relation est-une entre des objets , par exemple : un chien est un animal et un chat est un animal.

La combinaison représente une relation a-a entre des objets , tels que des voitures.

 

La relation entre une voiture et ses pneus, son moteur, son volant, ses systèmes embarqués, etc. devrait être une combinaison, car la voiture est composée de ces composants.

// 轮胎类
class Tire{
	// ...
} 

// 发动机类
class Engine{
    // ...
} 

// 车载系统类
class VehicleSystem{
	// ...
}

class Car{
    private Tire tire; // 可以复用轮胎中的属性和方法
    private Engine engine; // 可以复用发动机中的属性和方法
    private VehicleSystem vs; // 可以复用车载系统中的属性和方法
    // ...
} 

// 奔驰是汽车
class Benz extend Car{
	// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}

        La combinaison et l'héritage peuvent permettre la réutilisation du code. Si vous utilisez l'héritage ou la combinaison, vous devez choisir en fonction du scénario d'application. Conseil général : utilisez la combinaison autant que possible.

Articles sur Internet : Compréhension approfondie de la composition et de l'héritage en Java

【2】Polymorphisme

【2.1】Le concept de polymorphisme

        Le concept de polymorphisme : En termes simples, cela signifie des formes multiples. Le but spécifique est de compléter un certain comportement. Lorsque différents objets le complètent, différents états seront produits. 

【Par exemple】

 

【Par exemple】

 

En général : la même chose, arrivant à différents objets, produira des résultats différents.

【2.2】Conditions de mise en œuvre polymorphes

Pour réaliser le polymorphisme en Java, les conditions suivantes doivent être remplies, toutes indispensables :

  1. Doit être soumis au système d'héritage .

  2. Les sous - classes doivent remplacer les méthodes de la classe parent .

  3. Appelez la méthode remplacée via une référence à la classe parent .

class Animal {
    String name;
    int age;
    public Animal(String name, int age){
        this.name = name;
        this.age = age;
    }
    public void eat(){
        System.out.println(name + "吃饭");
    }
}

class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat(){
        System.out.println(name + "吃鱼~~~~");
    }
}

class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(name+"吃骨头~~~");
    }
}

public class Main {
    public static void eat(Animal a) {
        a.eat();
    }

    public static void main(String[] args) {
        Cat cat = new Cat("元宝", 20);
        Dog dog = new Dog("来福", 21);

        eat(cat);
        eat(dog);
    }
}

        Dans le code ci - dessus, le Code au - dessus de la ligne de séparation est écrit par l'implémenteur de la classe , et le Code sous la ligne de séparation est écrit par l'appelant de la classe .Lorsque l'appelant de la classe écrit la méthode eat, le type de paramètre est Animal (classe parent), à ce moment, il ne sait pas , et il ne fait pas non plus attention à quel type (quelle sous-classe) l'instance vers laquelle pointe la référence actuelle. À ce moment, lorsque la référence a appelle le manger méthode, il peut y avoir de nombreuses performances différentes. (par rapport à l'instance référencée par a), ce comportement est appelé polymorphisme

 

【2.3】Réécriture

        Remplacement : également appelé remplacement. La réécriture est la réécriture par une sous-classe du processus d'implémentation de méthode de modification non statique, non privée, de modification non finale et non constructeur de la classe parent. Ni la valeur de retour ni les paramètres formels ne peuvent être modifiés . Autrement dit, le shell reste inchangé et le noyau est réécrit ! L'avantage de la substitution est que les sous-classes peuvent définir leur propre comportement selon leurs besoins. En d’autres termes, les sous-classes peuvent implémenter les méthodes de la classe parent selon les besoins.

[ Règles de réécriture des méthodes ]

  • Lorsqu'une sous-classe remplace une méthode de classe parent, elle doit généralement être cohérente avec le prototype de la méthode de classe parent : le nom de la méthode du type de valeur de retour (liste de paramètres) doit être complètement cohérent.

  • Le type de valeur de retour de la méthode remplacée peut être différent, mais il doit avoir une relation parent-enfant

  • Les autorisations d'accès ne peuvent pas être inférieures aux autorisations d'accès des méthodes remplacées dans la classe parent. Par exemple : si une méthode de classe parent est modifiée par public, alors la méthode remplacée dans la sous-classe ne peut pas être déclarée protégée.

  • Les méthodes et constructeurs modifiés par static ou private dans la classe parent ne peuvent pas être remplacés.

  • Les méthodes remplacées peuvent être explicitement spécifiées à l'aide de l'annotation @Override. Cette annotation peut nous aider à effectuer certaines vérifications de légalité. Par exemple, si le nom de la méthode est accidentellement mal orthographié (comme aet), le compilateur le fera. Vous constaterez qu'il n'y a pas aet dans la classe parent, et une erreur de compilation sera signalée, indiquant que le remplacement ne peut pas être constitué.

[ La différence entre la réécriture et la surcharge ]

Différence passer outre passer outre
liste de paramètres Ne doit pas être modifié Doit être modifié
Type de retour Ne doit pas être modifié [sauf si une relation parent-enfant peut être formée] Peut être modifié
qualificatif d'accès Ne doit pas imposer de restrictions plus strictes (les restrictions peuvent être réduites) Peut être modifié

Autrement dit : la surcharge de méthode est une manifestation polymorphe d'une classe, tandis que la substitution de méthode est une manifestation polymorphe d'une sous-classe et d'une classe parent.

[ Principes de conception réécrits ]

        Pour les classes déjà utilisées, essayez de ne pas les modifier. La meilleure solution consiste à redéfinir une nouvelle classe, à réutiliser son contenu commun et à ajouter ou modifier du nouveau contenu.

        Par exemple : il y a quelques années, les téléphones mobiles ne pouvaient passer des appels et envoyer des messages texte, et l'identification de l'appelant ne pouvait afficher que des numéros. Cependant, les téléphones mobiles d'aujourd'hui peuvent non seulement afficher des numéros, mais également des avatars, des régions, etc. lors de l'affichage de l'identification de l'appelant. . Au cours de ce processus, nous ne devons pas apporter de modifications à l'ancienne classe d'origine, car la classe d'origine peut encore être utilisée par les utilisateurs . L'approche correcte est de créer une nouvelle classe de téléphone mobile et de réécrire la méthode d'identification de l'appelant. , répondant ainsi à nos besoins actuels. .

 

Liaison statique : également connue sous le nom de liaison anticipée (liaison anticipée), c'est-à-dire qu'au moment de la compilation, la méthode spécifique à appeler est déterminée en fonction du type de paramètres réels transmis par l'utilisateur. Surcharge de fonction représentative typique.

Liaison dynamique : également connue sous le nom de liaison tardive (liaison tardive), c'est-à-dire que le comportement de la méthode ne peut pas être déterminé au moment de la compilation. Vous devez attendre que le programme soit en cours d'exécution pour déterminer la méthode spécifique d'appel de cette classe.

【2.4】Transfert vers le haut et transformation vers le bas

【2.4.1】Transformation vers le haut

Transformation vers le haut : elle crée en fait un objet de sous-classe et l'utilise comme objet de classe parent. Format de syntaxe : nom de l'objet de type de classe parent = nouveau type de sous-classe ().

Animal animal = new Cat("元宝",2);

Animal est un type de classe parent, mais il peut référencer un objet de sous-classe car il est converti d'une petite plage à une grande plage.

 

【scènes à utiliser】

  • affectation directe

  • Paramètres de transmission de méthode

  • la méthode renvoie

class Animal {
    String name;
    int age;
    public Animal(String name, int age){
        this.name = name;
        this.age = age;
    }
    public void eat(){
        System.out.println(name + "吃饭");
    }
}

class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat(){
        System.out.println(name + "吃鱼~~~~");
    }
}

class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(name+"吃骨头~~~");
    }
}

public class Main {
    public static void eat(Animal a) {
        a.eat();
    }

    public static Animal buyAnimal(String strVar) {
        if("狗".equals(strVar)) {
            return new Dog("来福", 21);
        } else if("猫".equals(strVar)) {
            return new Cat("元宝", 20);
        } else {
            return new Animal("动物", 0);
        }
    }

    public static void main(String[] args) {
        // 1、直接赋值
        Animal animal01 = new Animal("动物", 0);
        animal01.eat(); // 动物吃饭

        Animal animal02 = new Cat("元宝", 20);
        animal02.eat(); // 元宝吃鱼~~~~

        Animal animal03 = new Dog("来福", 21);
        animal03.eat(); // 来福吃骨头~~~

        // 2、方法传参
        eat(animal01);  // 动物吃饭
        eat(animal02);  // 元宝吃鱼~~~~
        eat(animal03);  // 来福吃骨头~~~

        // 3、方法返回值
        Animal animal = buyAnimal("动物");
        animal.eat();   // 动物吃饭
        Animal cat = buyAnimal("猫");
        cat.eat();      // 元宝吃鱼~~~~
        Animal dog = buyAnimal("狗");
        dog.eat();      // 来福吃骨头~~~
    }
}

Avantages de la transformation ascendante : Rendre la mise en œuvre du code plus simple et plus flexible.

Défauts de transformation ascendante : les méthodes propres aux sous-classes ne peuvent pas être appelées.

【2.4.2】Transformation vers le bas

Lorsqu'un objet de sous-classe est utilisé comme méthode de classe parent après une transformation ascendante, la méthode de sous-classe ne peut plus être appelée. Cependant, vous devrez parfois appeler une méthode spécifique à la sous-classe. Dans ce cas : restaurez la référence de la classe parent à la sous-classe objet. , c’est-à-dire une conversion vers le bas.


public class TestAnimal {
    public static void main(String[] args) {
        Cat cat = new Cat("元宝",2);
        Dog dog = new Dog("小七", 1);
        
        // 向上转型
        Animal animal = cat;
        animal.eat();
        animal = dog;
        animal.eat();
        
        // 编译失败,编译时编译器将animal当成Animal对象处理
        // 而Animal类中没有bark方法,因此编译失败
        // animal.bark();
        // 向上转型
        // 程序可以通过编程,但运行时抛出异常---因为:animal实际指向的是狗
        // 现在要强制还原为猫,无法正常还原,运行时抛出:ClassCastException
        cat = (Cat)animal;
        cat.mew();
        
        // animal本来指向的就是狗,因此将animal还原为狗也是安全的
        dog = (Dog)animal;
        dog.bark();
    }
}

        Le downcasting est rarement utilisé et n'est pas sûr. Si la conversion échoue, une exception sera levée lors de l'exécution. Afin d'améliorer la sécurité du downcasting en Java, instanceof a été introduit.Si  l'expression est vraie, elle peut être convertie en toute sécurité.

public class TestAnimal {
    public static void main(String[] args) {
        Cat cat = new Cat("元宝",2);
        Dog dog = new Dog("小七", 1);
        
		// 向上转型
        Animal animal = cat;
        animal.eat();
        animal = dog;
        animal.eat();
        
        if(animal instanceof Cat){
            cat = (Cat)animal;
            cat.mew();
        } 
        
        if(animal instanceof Dog){
            dog = (Dog)animal;
            dog.bark();
        }
    }
}

Introduction officielle au mot-clé instanceof : Chapitre 15. Expressions

【2.5】Avantages et inconvénients du polymorphisme


class Shape {
    public void draw() {
        System.out.println("画图形");
    }
}

class Rect extends Shape{
    @Override
    public void draw() {
        System.out.println("♦");
    }
}

class Cycle extends Shape{
    @Override
    public void draw() {
        System.out.println("●");
    }
}

class Flower extends Shape{
    @Override
    public void draw() {
        System.out.println("❀");
    }
}

[ Avantages de l'utilisation du polymorphisme ]

  1. Cela peut réduire la « complexité cyclomatique » du code et éviter d'utiliser beaucoup de if-else

  • Qu'est-ce que la « complexité cyclomatique » ?

  • La complexité cyclomatique est une façon de décrire la complexité d'un morceau de code. Si un morceau de code est simple, il est relativement simple et facile à comprendre. S'il existe de nombreuses branches conditionnelles ou instructions de boucle, il est considéré comme plus compliqué à comprendre. comprendre.

  • Par conséquent, nous pouvons calculer simplement et grossièrement le nombre d'instructions conditionnelles et d'instructions de boucle dans un morceau de code. Ce nombre est appelé « complexité cyclomatique ». Si la complexité cyclomatique d'une méthode est trop élevée, une refactorisation doit être envisagée.

  • Différentes entreprises ont des spécifications différentes concernant la complexité cyclomatique du code. Généralement, elle ne dépassera pas 10.

Par exemple, ce que nous devons imprimer maintenant n'est pas une forme, mais plusieurs formes. Si ce n'est pas basé sur le polymorphisme, le code d'implémentation est le suivant :

public static void drawShapes() {
    Rect rect = new Rect();
    Cycle cycle = new Cycle();
    Flower flower = new Flower();
    String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};
    
    for (String shape : shapes) {
        if (shape.equals("cycle")) {
            cycle.draw();
        } else if (shape.equals("rect")) {
            rect.draw();
        } else if (shape.equals("flower")) {
            flower.draw();
        }
    }
}

Si vous utilisez le polymorphisme, vous n'avez pas besoin d'écrire autant d'instructions de branche if-else. Le code est plus simple. Le code d'implémentation est le suivant :

public static void drawShapes() {
	// 我们创建了一个 Shape 对象的数组.
	Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),new Rect(), new Flower()};
	
	for (Shape shape : shapes) {
		shape.draw();
	}
}
  1. Plus évolutif

Si vous souhaitez ajouter une nouvelle forme, le coût des modifications de code est relativement faible grâce au polymorphisme.

class Triangle extends Shape {
	@Override
	public void draw() {
		System.out.println("△");
	}
}
  • Pour l'appelant de la classe (méthode drawShapes), il suffit de créer une instance d'une nouvelle classe, et le coût de modification est très faible.

  • Pour les situations où le polymorphisme n'est pas utilisé, le if - else dans drawShapes doit être modifié dans une certaine mesure, et le coût de modification est plus élevé.

[Défaut polymorphe]

  • L'efficacité d'exécution du code est réduite.

  • Il n'y a pas de polymorphisme dans les attributs. Lorsque la classe parent et la classe enfant ont des attributs portant le même nom, seuls les attributs membres de la classe parent peuvent être référencés via la référence de classe parent.

  • Il n'y a pas de polymorphisme dans les constructeurs [voir Section 2.6].

【2.6】Évitez d'appeler des méthodes remplacées dans le constructeur

Un morceau de code avec des pièges. Nous créons deux classes, B est la classe parent et D est la sous-classe. Remplacez la méthode func dans D. Et appelez func dans le constructeur de B.

class B {
    public B() {
		// do nothing
    	func();
    }
    public void func() {
        System.out.println("B.func()");
    }
}
class D extends B {
    private int num = 1;
    @Override
    public void func() {
        System.out.println("D.func() " + num);
    }
}
public class Test {
    public static void main(String[] args) {
        D d = new D();
    }
} 
// 执行结果
D.func() 0
  • Lors de la construction de l'objet D, le constructeur de B sera appelé.

  • La méthode func est appelée dans le constructeur de B. A ce moment, la liaison dynamique sera déclenchée et la func dans D sera appelée.

  • À l'heure actuelle, l'objet D lui-même n'a pas été construit et num est dans un état non initialisé, avec une valeur de 0. S'il est polymorphe, la valeur de num doit être 1.

  • Par conséquent, au sein du constructeur, essayez d’éviter d’utiliser des méthodes d’instance, à l’exception des méthodes finales et privées.

[Conclusion] "Utilisez le moyen le plus simple possible pour mettre l'objet dans un état de fonctionnement" et essayez de ne pas appeler de méthodes dans le constructeur (si cette méthode est remplacée par une sous-classe, la liaison dynamique sera déclenchée, mais l'objet de la sous-classe n'a pas été construit) Terminé), il peut y avoir des problèmes cachés extrêmement difficiles à trouver.

Je suppose que tu aimes

Origine blog.csdn.net/lx473774000/article/details/131928999
conseillé
Classement