Guide de l'héritage en Java

1. Vue d'ensemble

L'un des principes fondamentaux de la programmation orientée objet - l' héritage - nous permet de réutiliser du code existant ou d'étendre un type existant.

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. La nécessité de l'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 peuvent offrir des caractéristiques différentes comme un toit ouvrant ou des fenêtres pare-balles, ils comprendraient tous des composants et des caractéristiques communs, comme le moteur et les roues.

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

De la même manière, 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 faire référence à un type hérité d'un autre type, en particulier:

  • un type de base est également appelé un type super ou parent
  • un type dérivé est appelé type étendu, sous ou enfant

3. Héritage de classe

3.1. Extension d'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 base Car :

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

La classe ArmoredCar peut hériter des membres de la classe Car en utilisant le mot - clé extend dans sa déclaration :

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

Nous pouvons maintenant dire que la classe ArmoredCar est une sous-classe de Car, et cette dernière est une superclasse d' ArmoredCar.

Les classes en Java prennent en charge l'héritage unique ; la classe ArmoredCar ne peut pas étendre plusieurs classes.

Notez également qu'en l'absence d'un étend mot - clé, une classe implicitement la classe hérite java.lang.Object .

Une classe de sous-classe hérite des membres protégés et publics non statiques de la classe de superclasse. De plus, les membres avec accès par défaut et au package sont hérités si les deux classes sont dans le même package.

En revanche, les membres privés et statiques d'une classe ne sont pas hérités.

3.2. Accès aux membres parents à partir d'une classe enfant

Pour accéder aux propriétés ou méthodes héritées, nous pouvons simplement les utiliser directement:

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

Notez que nous n'avons pas besoin d'une référence à la superclasse pour accéder à ses membres.

4. Héritage d'interface

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 l' ArmoredCar que nous avons défini dans la section précédente est nécessaire pour un super espion. La société de construction automobile a 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, nous remarquons l'utilisation du mot-clé implements pour hériter d'une interface.

4.2. Problèmes d'héritage multiple

Java permet l'héritage multiple à l'aide d'interfaces.

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

Voyons comment cette équation simple a changé avec l'introduction des méthodes par défaut dans les interfaces, avec Java 8.

À partir de Java 8, les interfaces peuvent choisir de définir des implémentations par défaut pour ses méthodes (une interface peut toujours définir des méthodes abstraites ). Cela signifie que si une classe implémente plusieurs interfaces, qui définissent des méthodes avec la même signature, la classe enfant hériterait d'implémentations séparées. 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éthode repair () .

Si les interfaces des exemples précédents définissent des variables avec le même nom, disons durée , nous ne pouvons pas y accéder sans précéder le nom de la variable avec le 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é extend . 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, en plus d'hériter de leurs 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 sur une interface (classe de base ou interface) , plutôt que de programmer sur leurs implémentations.

Par exemple, imaginez une condition dans laquelle une organisation tient une liste des voitures appartenant à ses employés. Bien entendu, tous les employés peuvent posséder différents modèles de voitures. 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 de Car héritent du type Car , les instances de classe dérivées peuvent être référencées à l'aide d'une variable de classe Car :

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

6. Membres de classe cachés

6.1. Membres d'instance masqués

Que se passe-t-il si la superclasse et la sous-classe définissent une variable ou une méthode avec le même nom ? Ne t'inquiète 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és this ou super .

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 ce mot- clé et des super mots - clés pour indiquer explicitement la variable ou la méthode à laquelle ils font référence. Cependant, les utiliser avec tous les membres peut rendre notre code encombré.

6.2. Membres statiques masqués

Que se passe-t-il lorsque notre classe de base et nos sous-classes définissent des variables statiques et des méthodes portant le même nom ? Pouvons-nous accéder à un membre statique à partir de 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 super mot - clé non statique dans msg () .

Puisque les membres statiques appartiennent à une classe, nous pouvons modifier l'appel précédent comme suit:

return Car.msg();

Prenons l'exemple suivant, dans lequel la classe de base et la classe dérivée définissent une méthode statique msg () 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 » et second.msg () affichera «ArmoredCar». Le message statique appelé dépend du type de variable utilisée pour faire référence à l' instance ArmoredCar .

7. Conclusion

Dans cet article, nous avons couvert un aspect essentiel du langage Java: l'héritage.

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

Comme toujours, le code source complet des exemples est disponible à l'adresse over sur GitHub.