Héritage

Introduction

L'héritage est un concept de la programmation orientée objet.

Il permet de :

  • Déterminer une hiérarchie dans les classes et les regrouper par familles.
  • Factoriser des comportements et des caractéristiques communes.
  • Redéfinir des comportements.

Caractéristiques du langage Java :

  • Toutes classes Java hérite de java.lang.Object.
  • Une classe concrète ou abstraite ne peut hériter que d'une seule classe concrète ou abstraite.
  • Une classe concrète ou abstraite peut hériter de plusieurs interfaces.
  • Une interface peut hériter de plusieurs interfaces.
  • Une énumération ne peut pas être héritée, ou hériter de classes, mais il peut implémenter des interfaces.
  • Une annotation ne supporte pas l'héritage, mais bizarrement peut être héritée.

Mise en œuvre

Quelques mots clef sont à connaitre pour utiliser l'héritage en Java:

  • protected : Niveau de visibilité protégé, visible par les classes enfants et classe du même package.
  • extends : Dans la déclaration d'une classe, permet de spécifier la classe à hériter.
  • implements : Dans la déclaration d'une classe, permet de spécifier les interfaces à hériter.
  • final :
    • Dans la déclaration d'une classe, bloque l'héritage de cette dernière (pas de classes enfants possibles).
    • Dans la déclaration d'une méthode, bloque la redéfinition de méthode pour les classes enfants.

Point particulier

Héritage pour une classe :

public abstract class AbstractEntity { /* ... */ }

public class Cutomer extends AbstractEntity implements Serializable, Comparable<Customer> {
    /* ... */
}

Héritage pour une interface :

public interface Repository<E> { /* ... */ }

public interface CustomerRepository extends Serializable, Repository<Customer> {
    /* ... */
}

Cas d'usage

Considérons les classes suivantes:

package fr.zelmoacademy.sample;

import java.time.LocalDate;

public class Cat {

    private String name;
    private Float size;
    private Float weight;
    private LocalDate birthDate;

    public Cat() {
    }

    public void talk() {
        System.out.println("Meow");
    }

    public void sleep() {
        System.out.println("Purrr");
    }

    public void huntMice() {
        // ...
    }

    // Accesseurs & Mutateurs
    // ...
}
package fr.zelmoacademy.sample;

import java.time.LocalDate;

public class Dog {

    private String name;
    private Float size;
    private Float weight;
    private LocalDate birthDate;

    public Dog() {
    }

    public void talk() {
        System.out.println("Woof");
    }

    public void sleep() {
        System.out.println("Zzz");
    }

    public void sniff() {
        // ...
    }

    // Accesseurs & Mutateurs
    // ...
}

Cet exemple caricatural met en évidence des caractéristiques communes entre les deux classes. On peut donc factoriser du code dans une classe abstraite :

package fr.zelmoacademy.sample;

import java.time.LocalDate;
import java.time.Period;

// Mot clef 'abstract' pour indiquer que cette classe est abstraite
// et donc ne peut pas être instancié directement.
public abstract class Pet {

    // Attributs à visibilité 'protected'
    // Visible par les classes enfants et les classes du même 'package'.

    protected String name;
    protected Float size;
    protected Float weight;
    protected LocalDate birthDate;

    // Constructeur par défaut
    // Visibilité 'protected' recommandée.
    protected Pet() {
    }

    // Méthode abstraite
    // Les classes enfants seront obligées de définir un comportement.
    public abstract void talk();

    // Méthode concrète
    // Les classes enfants peuvent redéfinir un comportement.
    public void sleep() {
        System.out.println("...zzz...");
    }

    // Mot clef 'final'.
    // Méthode concrète verrouillée.
    // Les classes enfants ne peuvent pas la redéfinir. 
    public final Period getAge(){
        return Period.between(LocalDate.now(), birthDate);
    }

    // Accesseurs & Mutateurs
    // ...
}

Nouvelles classes réusinées :

package fr.zelmoacademy.sample;

// Mot clef 'exetends' pour spécifier le nom de la classe parente.
public class Cat extends Pet {

    public Cat() {
    }

    // Annotation 'Override'
    // Implémente le comportement spécifique à cette classe.
    @Override
    public void talk() {
        System.out.println("Meow");
    }

    // Annotation 'Override'
    // Implémente le comportement spécifique à cette classe.
    // Ne tient pas en compte le comportement de la classe parente.
    @Override
    public void sleep() {
        System.out.println("Purrr");
    }

    // Méthode spécifique à cette classe.
    public void huntMice() {
        // ...
    }

}
package fr.zelmoacademy.sample;

// Mot clef 'exetends' pour spécifier le nom de la classe parente.
// Mot clef 'final', cette classe ne peut plus être héritée.
public final class Dog extends Pet {

    public Dog() {
    }

    // Annotation 'Override'
    // Implémente le comportement spécifique à cette classe.
    @Override
    public void talk() {
        System.out.println("Woof");
    }

    // Annotation 'Override'
    // Implémente le comportement spécifique à cette classe.
    // Ne tient pas en compte le comportement de la classe parente.
    @Override
    public void sleep() {

        // Invocation de la méthode parente, tel qu'elle a été défini par cette dernière.
        super.sleep();

        // Implémentation spécifique de cette classe.
        System.out.println("Zzz");
    }

    // Méthode spécifique à cette classe.
    public void sniff() {
        // ...
    }

}