CodeGym /Blog Java /France /Les classes abstraites en Java - Exemples spécifiques
Auteur
Vasyl Malik
Senior Java Developer at CodeGym

Les classes abstraites en Java - Exemples spécifiques

Publié dans le groupe France
Coucou ! Dans les leçons précédentes, nous avons rencontré les interfaces et appris à quoi elles servaient. Le sujet d'aujourd'hui fait écho au précédent. Parlons des classes abstraites en Java.

Pourquoi parle-t-on de classe abstraite

Tu te souviens probablement de ce qu'est l'abstraction en Java, nous l'avons déjà évoquée. :) Si tu as oublié ce qu'est une classe abstraite, pas de souci. Rappelle-toi : c'est un principe de la POO qui dit que lors de la conception de classes et la création d'objets, nous devons identifier seulement les propriétés essentielles d'une entité et abandonner ce qui n'est pas pertinent. Par exemple, si nous concevons une classe SchoolTeacher nous n'avons pas besoin d'une propriété de type 'taille'. En effet, cette propriété n'est pas pertinente pour un enseignant. Mais si nous créons une classe BasketballPlayer alors la taille serait une caractéristique importante. Alors écoute bien. Une classe abstraite est aussi abstraite qu'on peut l'imaginer : c'est un « espace vide » inachevé pour un groupe de classes futures. Le vide ne peut pas être utilisé tel quel. Il est trop « brut ». Mais il décrit certains états et comportements généraux que les classes futures héritant de la classe abstraite posséderont.Les classes abstraites en Java - Exemples spécifiques - 1

Exemple d'abstract pour une classe abstraite en Java

Prenons un exemple abstrait simple avec des voitures :

public abstract class Car {

   private String model;
   private String color;
   private int maxSpeed;

   public abstract void gas();

   public abstract void brake();

   public String getModel() {
       return model;
   }

   public void setModel(String model) {
       this.model = model;
   }

   public String getColor() {
       return color;
   }

   public void setColor(String color) {
       this.color = color;
   }

   public int getMaxSpeed() {
       return maxSpeed;
   }

   public void setMaxSpeed(int maxSpeed) {
       this.maxSpeed = maxSpeed;
   }
}
C'est à cela que ressemble une classe abstraite super simple. Comme tu peux le voir, rien de spécial ici :) Alors pourquoi en a-t-on besoin ? Tout d'abord, elle décrit notre entité requise, une voiture, de la manière la plus abstraite possible. Il y a une raison pour laquelle nous utilisons le mot abstract. Dans le monde réel, il n'y a pas de « voitures abstraites ». Il y a des camions, des voitures de course, des berlines, des coupés et des VUS. Notre classe abstraite est simplement un « plan » que nous utiliserons plus tard pour créer des classes de voiture.

public class Sedan extends Car {

   @Override
   public void gas() {
       System.out.println("The sedan is accelerating!");
   }

   @Override
   public void brake() {
       System.out.println("The sedan is slowing down!");
   }

}
C'est très semblable à ce dont nous avons parlé lors des leçons sur l'héritage. Mais dans ces leçons, nous avions une classe Car et ses méthodes n'étaient pas abstraites. Cependant, cette solution a un certain nombre d'inconvénients que les classes abstraites résolvent. Pour commencer, tu ne peux pas créer une instance d'une classe abstraite :

public class Main {

   public static void main(String[] args) {

       Car car = new Car(); // Error! The Car class is abstract!
   }
}
Les créateurs de Java ont délibérément conçu cette « fonctionnalité ». Une fois de plus, pour rappel : une classe abstraite n'est qu'un modèle pour les futures classes « normales ». Tu n'as pas besoin de copies d'un plan, pas vrai ? De la même manière, tu ne crées pas des instances d'une classe abstraite :) Mais si la classe Car n'était pas abstraite, nous pourrions facilement en créer des instances :

public class Car {

   private String model;
   private String color;
   private int maxSpeed;

   public void gas() {
       // Some logic
   }

    public void brake() {
       // Some logic
   }
}


public class Main {

   public static void main(String[] args) {

       Car car = new Car(); // Everything is fine. A car is created.
   }
}
Maintenant, notre programme a une sorte de voiture incompréhensible ; ce n'est pas un camion, pas une voiture de course, pas une berline, et il n'est absolument pas clair de quoi il s'agit. C'est la fameuse « voiture abstraite » qui n'existe pas dans la vie réelle. Nous pouvons prendre un exemple similaire avec des animaux. Imagine une classe Animal (des animaux abstraits). On ne sait pas de quel genre d'animal il s'agit, à quelle famille il appartient et quelles caractéristiques il a. Ce serait étrange de voir ça dans ton programme. Il n'y a pas d'« animaux abstraits » dans la nature. Seulement des chiens, des chats, des renards, des taupes, etc. Les classes abstraites nous affranchissent des objets abstraits. Elles nous donnent les états et le comportement de base. Par exemple, toutes les voitures doivent avoir un modèle, une couleur et une vitesse maximale, et tu devrais être en mesure de mettre les gaz et d'appliquer les freins. Et c'est tout. C'est un plan abstrait général. Ensuite, tu conçois les classes dont tu as besoin. Remarque : deux méthodes de la classe abstraite sont également marquées abstract et n'ont pas d'implémentation. La raison est la même que précédemment : les classes abstraites ne créent pas de comportement par défaut pour les voitures abstraites. Ces méthodes indiquent simplement ce que chaque voiture devrait être en mesure de faire. Toutefois, si tu as besoin d'un comportement par défaut, tu peux implémenter des méthodes dans une classe abstraite. Java n'interdit pas ce qui suit :

public abstract class Car {

   private String model;
   private String color;
   private int maxSpeed;

   public void gas() {
       System.out.println("Gas!");
   }

   public abstract void brake();

   // Getters and setters
}


public class Sedan extends Car {

   @Override
   public void brake() {
       System.out.println("The sedan is slowing down!");
   }

}

public class Main {

   public static void main(String[] args) {

       Sedan sedan = new Sedan();
       sedan.gas();
   }
}
Sortie de la console :
Gaz !
Comme tu peux le voir, nous avons implémenté la première méthode dans la classe abstraite, mais pas la seconde. En conséquence, le comportement de notre classe Sedan est divisé en deux parties : si tu appelles la méthode gas(), l'appel « remonte » jusqu'à la classe parente abstraite Car, mais nous avons remplacé la méthode brake() dans la classe Sedan. Cela s'avère être très pratique et flexible. Mais maintenant, notre classe n'est plus si abstraite ? Après tout, la moitié de ses méthodes sont implémentées. Il s'agit en fait d'une caractéristique très importante : une classe est abstraite si au moins une de ses méthodes est abstraite. Que ce soit une méthode sur deux ou sur mille, cela ne fait aucune différence. Nous pouvons même implémenter toutes les méthodes et n'en laisser aucune abstraite. Dans ce cas il s'agirait d'une classe abstraite sans méthodes abstraites. En principe, c'est possible, et le compilateur ne générera pas d'erreurs, mais il est préférable d'éviter cela : Le mot abstract perd son sens, et tes collègues programmeurs seront très surpris :/ Dans le même temps, si une méthode est marquée avec le mot abstract, chaque classe enfant doit l'implémenter ou la déclarer comme abstraite. Sinon, le compilateur génère une erreur. Bien sûr, chaque classe ne peut hériter que d'une seule classe abstraite, donc en termes d'héritage, il n'y a pas de différence entre les classes abstraites et ordinaires. Peu importe si nous héritons d'une classe abstraite ou ordinaire, il ne peut y avoir qu'une seule classe parent.

Pourquoi Java n'offre pas l'héritage multiple de classes

Nous avons déjà dit que Java n'offrait pas l'héritage multiple, mais nous n'avons pas vraiment expliqué pourquoi. Essayons de le faire maintenant. Le fait est que si Java disposait de l'héritage multiple, les classes enfant ne sauraient pas quel comportement spécifique choisir. Supposons que nous avons deux classes : Toaster et NuclearBomb :

public class Toaster {


 public void on() {

       System.out.println("The toaster is on. Toast is being prepared!");
   }

   public void off() {

       System.out.println("The toaster is off!");
   }
}


public class NuclearBomb {

   public void on() {

       System.out.println("Boom!");
   }
}
Comme tu peux le voir, les deux ont une méthode on(). Pour un grille-pain, elle commence à griller un toast. Pour une bombe nucléaire, elle déclenche une explosion. Oups : / Maintenant, imagine que tu as décidé (ne me demande pas pourquoi !) de créer quelque chose entre les deux. Et qu'ainsi tu te retrouves avec une classe MysteriousDevice ! Ce code, bien sûr, ne fonctionne pas, et nous ne le fournissons qu'à titre d'exemple, mais « on peut l'imaginer » :

public class MysteriousDevice extends Toaster, NuclearBomb {

   public static void main(String[] args) {

       MysteriousDevice mysteriousDevice = new MysteriousDevice();
       mysteriousDevice.on(); // So what should happen here? Do we get toast or a nuclear apocalypse?
   }
}
Voyons un peu ce que nous avons. Le dispositif mystérieux hérite simultanément de Toaster et de NuclearBomb. Les deux ont des méthodes on(). Par conséquent, si nous appelons la méthode on(), celle qui doit être appelée sur l'objet MysteriousDevice n'est pas claire. Il n'y a aucun moyen pour l'objet de le savoir. Et pour couronner le tout, la classe NuclearBomb n'a pas de méthode off(). Ainsi, si nous avons mal deviné, il serait impossible d'éteindre l'appareil. C'est précisément à cause de cette « confusion », où l'objet ne sait pas quel comportement exposer, que les créateurs de Java ont abandonné l'héritage multiple. Toutefois, tu te rappelles sûrement que les classes Java peuvent implémenter plusieurs interfaces. Soit dit en passant, lors de tes études, tu as déjà rencontré au moins une classe abstraite ! Même si tu ne l'avais peut-être même pas remarqué :)

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar>
C'est ta vieille amie, la classe Calendar. Elle est abstraite et a plusieurs enfants. Une d'elles est GregorianCalendar. Tu l'as déjà utilisée dans les leçons sur les dates. :) Tout est assez clair jusque-là ? Il ne reste qu'une question : quelle est la différence fondamentale entre les classes abstraites et les interfaces ? Pourquoi ont-ils ajouté les deux à Java au lieu de simplement limiter le langage à une seule solution ? Après tout, cela aurait été tout à fait convenable, non ? Nous en discuterons lors de la prochaine leçon! En attendant, reste avec nous :)
Cet article est également disponible en anglais:
Read the English version of this article to explore an abstract class in Java. Abstraction in Java is super important and super cool!
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION