Guide sur l’héritage en Java

Guide de l'héritage en Java

1. Vue d'ensemble

L'un des principes fondamentaux de la programmation orientée objet -inheritance – enables us to reuse existing code or extend an existing type.

En termes simples, en Java, une classe peut hériter d'une autre classe et de plusieurs interfaces, tandis qu'une interface peut hériter d'autres interfaces.

Dans cet article, nous allons commencer par la nécessité de l'héritage, en passant à la façon dont l'héritage fonctionne avec les classes et les interfaces.

Ensuite, nous verrons comment les noms de variable / méthode et les modificateurs d'accès affectent les membres hérités.

Et à la fin, nous verrons ce que signifie hériter d'un type.

2. Le besoin d'héritage

Imaginez, en tant que constructeur automobile, vous proposez plusieurs modèles de voitures à vos clients. Même si différents modèles de voitures offrent des fonctionnalités différentes, telles qu'un toit ouvrant ou des vitres à l'épreuve des balles, elles incluent toutes des composants et fonctionnalités communs, tels que le moteur et les roues.

Il est logique de créer une conception de base et de l’étendre pour créer ses versions spécialisées, plutôt que de concevoir chaque modèle de voiture séparément, à partir de zéro.

De manière similaire, avec l'héritage, nous pouvons créer une classe avec des fonctionnalités et un comportement de base et créer ses versions spécialisées, en créant des classes, qui héritent de cette classe de base. De la même manière, les interfaces peuvent étendre les interfaces existantes.

Nous remarquerons l'utilisation de plusieurs termes pour désigner un type hérité d'un autre type, en particulier:

  • un type de base est aussi appelé un type super ou un type parent

  • un type dérivé est appelé type étendu, sub ou enfant

3. Héritage avec classes

3.1. Prolonger une classe

Une classe peut hériter d'une autre classe et définir des membres supplémentaires.

Commençons par définir une classe de baseCar:

public class Car {
    int wheels;
    String model;
    void start() {
        // Check essential parts
    }
}

La classeArmoredCar peut hériter des membres de la classeCar parusing the keyword extends in its declaration:

public class ArmoredCar extends Car {
    int bulletProofWindows;
    void remoteStartCar() {
    // this vehicle can be started by using a remote control
    }
}

Classes in Java support single inheritance; la classeArmoredCar ne peut pas étendre plusieurs classes. En l'absence de mot cléextends, une classe hérite implicitement de la classejava.lang.Object.

3.2. Ce qui est hérité

Fondamentalement, une classe dérivée hérite des membresprotected etpublic de la classe de base, qui ne sont passtatic. De plus, les membres avec accèsdefault etpackage sont héritées si les deux classes sont dans le même package.

Une classe de base ne permet pas l'accès à tout son code par les classes dérivées.

Les membresprivate etstatic d'une classe ne sont pas hérités par une classe dérivée. De plus, si la classe de base et les classes dérivées sont définies dans des packages séparés, les membres avec un accèsdefault oupackage dans la classe de base ne sont pas hérités dans la classe dérivée.

3.3. Accéder aux membres de la classe parents à partir d'une classe dérivée

C'est simple. Utilisez-les simplement (nous n'avons pas besoin d'une référence à la classe de base pour accéder à ses membres). Voici un exemple rapide:

public class ArmoredCar extends Car {
    public String registerModel() {
        return model;
    }
}

3.4. Membres d'instance de classe de base masqués

Que se passe-t-il si notre classe de base et notre classe dérivée définissent toutes deux une variable ou une méthode portant le même nom? Ne vous inquiétez pas; nous pouvons toujours accéder aux deux. Cependant, nous devons rendre notre intention claire à Java, en préfixant la variable ou la méthode avec les mots-clésthis ousuper.

Le mot cléthis fait référence à l'instance dans laquelle il est utilisé. Le mot clésuper (comme cela semble évident) fait référence à l'instance de classe parente:

public class ArmoredCar extends Car {
    private String model;
    public String getAValue() {
        return super.model;   // returns value of model defined in base class Car
        // return this.model;   // will return value of model defined in ArmoredCar
        // return model;   // will return value of model defined in ArmoredCar
    }
}

De nombreux développeurs utilisent les mots clésthis etsuper pour indiquer explicitement à quelle variable ou à quelle méthode ils font référence. Cependant, les utiliser avec tous les membres peut rendre notre code encombré.

3.5. Membres statiques de classe de base masqués

Que se passe-t-il lorsque notre classe de base et nos classes dérivées définissent des variables statiques et des méthodes portant le même nom? Pouvons-nous accéder à un membrestatic depuis la classe de base, dans la classe dérivée, comme nous le faisons pour les variables d'instance?

Découvrons à l'aide d'un exemple:

public class Car {
    public static String msg() {
        return "Car";
    }
}
public class ArmoredCar extends Car {
    public static String msg() {
        return super.msg(); // this won't compile.
    }
}

Non, nous ne pouvons pas. Les membres statiques appartiennent à une classe et non à des instances. Nous ne pouvons donc pas utiliser le mot clé non statiquesuper dansmsg().

Les membres statiques appartenant à une classe, nous pouvons modifier l'appel précédent de la manière suivante:

return Car.msg();

Prenons l'exemple suivant, dans lequel la classe de base et la classe dérivée définissent une méthode statiquemsg() avec la même signature:

public class Car {
    public static String msg() {
        return "Car";
    }
}
public class ArmoredCar extends Car {
    public static String msg() {
        return "ArmoredCar";
    }
}

Voici comment nous pouvons les appeler:

Car first = new ArmoredCar();
ArmoredCar second = new ArmoredCar();

Pour le code précédent,first.msg() affichera «Car etsecond.msg() affichera« ArmoredCar ». Le message statique appelé dépend du type de variable utilisée pour faire référence à l'instance deArmoredCar.

4. Héritage avec des interfaces

4.1. Implémentation de plusieurs interfaces

Bien que les classes ne puissent hériter qu'une seule classe, elles peuvent implémenter plusieurs interfaces.

Imaginez que leArmoredCar que nous avons défini dans la section précédente est requis pour un super espion. La société de fabrication deCara donc pensé à ajouter des fonctionnalités volantes et flottantes:

public interface Floatable {
    void floatOnWater();
}
public interface Flyable {
    void fly();
}
public class ArmoredCar extends Car implements Floatable, Flyable{
    public void floatOnWater() {
        System.out.println("I can float!");
    }

    public void fly() {
        System.out.println("I can fly!");
    }
}

Dans l'exemple ci-dessus, on remarque l'utilisation du mot-cléimplements pour hériter d'une interface.

4.2. Problèmes d'héritage multiple

L'héritage multiple avec des interfaces est autorisé en Java.

Jusqu'à Java 7, ce n'était pas un problème. Les interfaces ne pouvaient définir que des méthodesabstract, c'est-à-dire des méthodes sans aucune implémentation. Ainsi, si une classe implémentait plusieurs interfaces avec la même signature de méthode, le problème ne se posait pas. La classe d'implémentation n'a finalement eu qu'une méthode à implémenter.

Voyons comment cette équation simple a changé avec l’introduction des méthodesdefault dans les interfaces, avec Java 8.

Starting with Java 8, interfaces could choose to define default implementations for its methods (une interface peut toujours définir les méthodesabstract). Cela signifie que si une classe implémente plusieurs interfaces définissant des méthodes avec la même signature, la classe enfant hériterait d'implémentations distinctes. Cela semble complexe et n'est pas autorisé.

Java interdit l'héritage de plusieurs implémentations des mêmes méthodes, définies dans des interfaces distinctes.

Voici un exemple:

public interface Floatable {
    default void repair() {
        System.out.println("Repairing Floatable object");
    }
}
public interface Flyable {
    default void repair() {
        System.out.println("Repairing Flyable object");
    }
}
public class ArmoredCar extends Car implements Floatable, Flyable {
    // this won't compile
}

Si nous voulons implémenter les deux interfaces, nous devrons remplacer la méthoderepair().

Si les interfaces des exemples précédents définissent des variables avec le même nom, disonsduration, nous ne pouvons pas y accéder sans faire précéder le nom de la variable du nom de l’interface:

public interface Floatable {
    int duration = 10;
}
public interface Flyable {
    int duration = 20;
}
public class ArmoredCar extends Car implements Floatable, Flyable {

    public void aMethod() {
        System.out.println(duration); // won't compile
        System.out.println(Floatable.duration); // outputs 10
        System.out.println(Flyable.duration); // outputs 20
    }
}

4.3. Interfaces étendant d'autres interfaces

Une interface peut étendre plusieurs interfaces. Voici un exemple:

public interface Floatable {
    void floatOnWater();
}
interface interface Flyable {
    void fly();
}
public interface SpaceTraveller extends Floatable, Flyable {
    void remoteControl();
}

Une interface hérite d'autres interfaces en utilisant le mot-cléextends. Les classes utilisent le mot-cléimplements pour hériter d'une interface.

5. Type d'héritage

Lorsqu'une classe hérite d'une autre classe ou d'interfaces, hormis ses membres, elle hérite également de leur type. Cela s'applique également à une interface qui hérite d'autres interfaces.

C'est un concept très puissant, qui permet aux développeurs de programmer une interface (classe de base ou interface), plutôt que de programmer leurs implémentations (classes concrètes ou dérivées).

Par exemple, imaginons une condition dans laquelle une organisation conserve une liste des voitures appartenant à ses employés. Bien entendu, tous les employés peuvent posséder différents modèles de voiture. Alors, comment pouvons-nous faire référence à différentes instances de voiture? Voici la solution:

public class Employee {
    private String name;
    private Car car;

    // standard constructor
}

Étant donné que toutes les classes dérivées deCar héritent du typeCar, les instances de classe dérivées peuvent être référencées en utilisant une variable de classeCar:

Employee e1 = new Employee("Shreya", new ArmoredCar());
Employee e2 = new Employee("Paul", new SpaceCar());
Employee e3 = new Employee("Pavni", new BMW());

6. Conclusion

Dans cet article, nous avons abordé un aspect fondamental du langage Java: le fonctionnement de l'héritage.

Nous expliquons comment Java prend en charge l'héritage unique avec des classes et l'héritage multiple avec des interfaces et discutons des subtilités du fonctionnement du mécanisme dans le langage.

Comme toujours, le code source complet des exemples est disponibleover on GitHub.