LECON 204


Les classes abstraites
Nous voici de retour dans l'un des fondements du langage Java.
En effet, vous verrez ici que les classes abstraites sont vraiment pratiques.
Sans plus attendre : let's go !

Sommaire du chapitre :

  • Qu'est-ce que c'est ?
  • Une classe Animal très abstraite
  • Étoffons notre exemple
  • Astuce d'Eclipse
  • Ce qu'il faut retenir

Qu'est-ce que c'est ?

Ne vous en faites pas... Ici abstraite n'a rien de commun avec un certain mouvement de peinture.
En fait, une classe abstraite est quasiment comme une classe normale. Oui, comme une classe que vous avez maintenant l'habitude de coder.
Ceci dit, elle a tout de même une particularité :
vous ne pouvez pas l'instancier !

Vous avez bien entendu. Imaginons que nous ayons une classe A déclarée abstraite. Ce code ne compilera pas :
Code : Java -  
1
2
3
4
5
6
7
8
public class Test{
 
   public static void main(String[] args){
      
       A obj = new A();//Erreur de compilation ! ! 
 
   }
}

À quoi ça sert, alors ?

J'attendais cette question... C'est pour ça que je n'ai pas commencé directement par un exemple de classe abstraite.
Tout d'abord, je vais vous donner un exemple de situation (de programme, en fait).
Imaginez que vous êtes en train de réaliser un programme qui gère différents types d'animaux (oui, je sais : l'exemple est bête, mais il a le mérite d'être simple à comprendre).

Dans ce programme, vous avez :
  • des loups
  • des chiens
  • des chats
  • des lions
  • des tigres.


Je pense tout de même que vous n'allez pas faire toutes vos classes bêtement... il va de soi que tous ces animaux ont des choses en commun ! Et qui dit chose en commun... dit héritage.
Que pouvons-nous définir de commun à tous ces animaux, sinon :
  • une couleur
  • un poids
  • qu'ils crient
  • qu'ils se déplacent
  • qu'ils mangent
  • qu'ils boivent.


Nous pouvons donc faire une classe mère, appelons-la Animal.
Avec ce que nous avons dégagé de commun, nous pouvons lui définir des attributs et des méthodes. Voici donc à quoi pourraient ressembler nos classes pour le moment :



Nous avons bien notre classe mère Animal et nos animaux qui en héritent.
À présent, laissez-moi vous poser une question.
Vu que notre classe Animal est public dans notre cas, qu'est-ce qu'est censé faire un objet Animal ? Quel est son poids, sa couleur, que mange-t-il ?

Si nous avons un morceau de code qui ressemble à ceci :
Code : Java -  
1
2
3
4
5
6
public class Test{
   public static void main(String[] args){
      Animal ani = new Animal();
      ani.manger();//Que doit-il faire ? ?
   }
}

...personnellement, je ne sais pas comment mange un objet Animal...
Vous conviendrez que toutes les classes ne sont pas bonnes à être instanciées !

C'est là que rentrent en jeu nos classes abstraites. En fait, ces classes servent à définir une super classe.
D'accord, mais comment on empêche une classe d'être instanciable puisque tu nous a dit que la JVM déclare un constructeur par défaut... On ne peut donc pas omettre le constructeur !

Tout à fait. Pour répondre à cette question : suivez le guide !

Une classe Animal très abstraite

En fait, il existe une règle pour qu'une classe soit considérée comme abstraite. Elle doit être déclarée avec le mot clé abstract.

Voici un exemple illustrant mes dires :

Classe déclarée abstraite :
Code : Java -  
1
2
3
abstract class Animal{
 
}


Une telle classe peut avoir le même contenu qu'une classe normale. Ses enfants pourront utiliser tous ses éléments déclarés (attributs et méthodes) public. Cependant, ce type de classe permet de définir des méthodes abstraites. Ces méthodes ont une particularité ; elles n'ont pas de corps !

En voici un exemple :
Code : Java -  
1
2
3
abstract class Animal{
   abstract void manger();//une méthode abstraite
}

Vous voyez pourquoi on dit "méthode abstraite", difficile de voir ce que cette méthode sait faire...
Retenez bien qu'une méthode abstraite n'est composée que de l'entête de la méthode suivie d'un point-virgule : ;

Jusque-là ça va, mais concrètement, à quoi ça sert ?

Tout d'abord, vous devez savoir qu'une méthode abstraite ne peut exister que dans une classe abstraite. Si dans une classe, vous avez une méthode déclarée abstraite, vous DEVEZ DÉCLARER CETTE CLASSE COMME ETANT ABSTRAITE.

Maintenant voyons à quoi cela peut servir. Vous avez vu les avantages de l'héritage et du polymorphisme. Dans ce cas, nos classes enfants hériteront aussi des classes abstraites mais, vu que celles-ci n'ont pas de corps, nos classes enfants seront OBLIGÉES de redéfinir ces méthodes !
De ce fait, nos classes enfants auront des méthodes polymorphes en leur sein et donc, la covariance des variables repointe le bout de son nez...
La covariance appliquée aux classes abstraites donne ceci :

Code : Java -  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Test{
 
   public static void main(String args[]){
 
     Animal loup = new Loup();
     Animal chien = new Chien();
     loup.manger();
     chien.crier();
 
   }
}

Attends ! Tu nous as dit qu'on ne pouvait instancier de classe abstraites ?

Et je maintiens mes dires. Ici, nous n'avons pas instancié notre classe abstraite. Nous avons instancié un objet Loup que nous avons mis dans un objet de type Animal : il en va de même pour l'instanciation de la classe Chien.
Vous devez vous rappeler que l'instance se crée avec le mot clé new. En aucun cas le fait de déclarer une variable d'un type de classe donnée (ici, Animal) est une instanciation ! Ici nous instancions un Loup et un Chien.


Vous pouvez aussi utiliser une variable de type Object comme référence pour un objet Loup, un objet Chien...
Vous savez déjà que ce code fonctionne :
Code : Java -  
1
2
3
4
5
public class Test{
  public static void main(String[] args){
     Object obj = new Loup();
  }
}


Par contre, ceci pose problème :
Code : Java -  
1
2
3
4
5
6
public class Test{
  public static void main(String[] args){
     Object obj = new Loup();
     Loup l = obj;//Problème de référence
  }
}


Eh oui ! Vous essayez ici de mettre une référence de type Object dans une référence de type Loup . Pour avertir la JVM que la référence que vous voulez mettre dans votre objet de type Loup est un Loup, vous devez utiliser le transtypage !
Revoyons notre code :
Code : Java -  
1
2
3
4
5
6
public class Test{
  public static void main(String[] args){
     Object obj = new Loup();
     Loup l = (Loup)obj;//Vous prévenez la JVM que la référence que vous passez est de type Loup
  }
}


Vous pourrez bien évidemment instancier directement un objet Loup, un objet Chien ou tout autre.
Pour le moment, nous n'avons aucun code dans aucune classe ! Les exemples que je vous ai fournis ne font rien du tout, mais ils seront censés fonctionner lorsque nous aurons mis des morceaux de code dans nos classes.

À présent, étoffons nos classes et notre diagramme avant d'avoir un code qui fonctionne bien !

Étoffons notre exemple

Nous allons donc rajouter des morceaux de code à nos classes.
Tout d'abord, voyons le topo de ce que nous savons.
  • Nos objets auront tous une couleur et un poids différents. Nos classes auront donc le droit de modifier ceux-ci.
  • Ici, nous partons du principe que tous nos animaux mangeront de la viande. La méthode manger() sera donc définie dans la classe Animal.
  • Idem pour la méthode boire(). Ils boiront tous de l'eau.
  • Par contre, ils ne crient pas et ne se déplaceront pas de la même manière. Nous ferons donc des méthodes polymorphes et déclarerons les méthodes crier() et deplacement() abstraites dans la classe Animal.


Voici ce que donneraient nos classes :


J'ai colorié la classe abstraite en bleu mais il y un autre moyen de savoir si une classe est abstraite :
le nom de celle-ci est en italique.
Je ne sais pas si c'est une convention ou non, mais argoUML la différencie de cette façon !

Nous voyons bien que notre classe Animal est déclarée abstraite et que nos classes filles héritent de celle-ci. De plus, nos classes filles ne redéfinissent que deux méthodes sur quatre, on en conclut ici que ces deux méthodes doivent être abstraites.
Nous ajouterons deux constructeurs à nos classes filles, un par défaut, ainsi qu'un avec les deux paramètres d'initialisation. À ceci nous ajouterons aussi les accesseurs d'usage. Cependant... nous pouvons améliorer un peu cette architecture, sans pour autant rentrer dans les détails !

Vu les animaux présents, nous aurions pu faire une sous-classe Carnivore, ou encore AnimalDomestique et AnimalSauvage... Ici, nous allons nous contenter de faire deux sous-classes Canin et Felin qui hériteront d'Animal et dont nos objets hériteront !
Nous allons redéfinir la méthode deplacement() dans cette classe car nous allons partir du principe que les félins se déplacent d'une certaine façon, et les canins d'une autre. Avec cet exemple, nous réviserons le polymorphisme...
Voilà notre diagramme mis à jour :


Vous avez vu ? J'ai ajouté une méthode toString() :D.

Voici les codes Java correspondant :

Animal.java



Code : Java -  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
abstract class Animal {
 
        /**
         * La couleur de l'animal
         */
        protected String couleur;
        /**
         * Le poids
         */
        protected int poids;
        
        /**
         * La méthode manger
         */ 
        protected void manger(){
                System.out.println("Je mange de la viande");
        }
        
        /**
         * La méthode boire
         */
        protected void boire(){
                System.out.println("Je bois de l'eau !");
        }
        
        /**
         * La méthode de déplacement
         */
        abstract void deplacement();
        /**
         * La méthode de cri 
         */
        abstract void crier();
        
        public String toString(){
                
                String str = "Je suis un objet de la " + this.getClass() + ", je suis " + this.couleur + ", je pèse " + this.poids;
                return str;
        }
        
}

Felin.java



Code : Java -  
1
2
3
4
5
6
7
8
public abstract class Felin extends Animal {
 
        @Override
        void deplacement() {
                System.out.println("Je me déplace seul !");
        }
 
}

Canin.java



Code : Java -  
1
2
3
4
5
6
7
8
public abstract class Canin extends Animal {
 
        @Override
        void deplacement() {
                System.out.println("Je me déplace en meute !");
        }
 
}

Chien.java



Code : Java -  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class Chien extends Canin {
 
        public Chien(){
                
        }
        public Chien(String couleur, int poids){
                this.couleur = couleur;
                this.poids = poids;
        }       
 
        
        void crier() {
                System.out.println("J'aboie sans raison ! ");
        }
 
}

Loup.java



Code : Java -  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Loup extends Canin {
 
        public Loup(){
                
        }
        public Loup(String couleur, int poids){
                this.couleur = couleur;
                this.poids = poids;
        }       
 
        void crier() {
                System.out.println("J'hurle à la lune en faisant ouhouh ! ! ");                
        }
}

Lion.java



Code : Java -  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class Lion extends Felin {
 
        public Lion(){
                
        }
        public Lion(String couleur, int poids){
                this.couleur = couleur;
                this.poids = poids;
        }       
 
        void crier() {
                System.out.println("Je rugis dans la savane !");
        }
 
}

Tigre.java



Code : Java -  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class Tigre extends Felin {
 
        public Tigre(){
                
        }
        public Tigre(String couleur, int poids){
                this.couleur = couleur;
                this.poids = poids;
        }
        
        void crier() {
                System.out.println("Je grogne très fort !");
        }
 
}

Chat.java



Code : Java -  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class Chat extends Felin {
 
        
        public Chat(){
                
        }
        public Chat(String couleur, int poids){
                this.couleur = couleur;
                this.poids = poids;
        }
        
        void crier() {
                System.out.println("Je miaule sur les toits !");
        }
 
}

Dis donc ! Une classe abstraite ne doit pas avoir une méthode abstraite ?

Je n'ai jamais dis ça ! Une classe déclarée abstraite n'est plus instanciable, mais elle n'est nullement obligée d'avoir des méthodes abstraites !
En revanche, une classe ayant une méthode abstraite doit être déclarée abstraite !

Maintenant que vous avez toutes vos classes,
faites des tests. Autant que vous le voulez.
Dans cet esprit :
Code : Java -  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class Test {
 
        /**
         * @param args
         */
        public static void main(String[] args) {
                Loup l = new Loup("Gris bleuté", 20);
                l.boire();
                l.manger();
                l.deplacement();
                l.crier();
                System.out.println(l.toString());
        }
 
}


Voilà le jeu d'essai de ce code :

Dans la méthode toString() de la classe Animal, j'ai utilisé la méthode getClass() qui -je vous le donne en mille- est dans la classe Object. Celle-ci retourne "class <nom de la classe>".


Dans cet exemple, nous pouvons voir que nous avons un objet Loup.
  • À l'appel de la méthode boire() : l'objet appelle la méthode de la classe Animal.
  • À l'appel de la méthode manger() : idem.
  • À l'appel de la méthode toString() : idem.
  • À l'appel de la methode deplacement() : c'est la méthode de la classe Canin qui est invoquée ici.
  • À l'appel de la méthode crier() : c'est la méthode de la classe Loup qui est appelée.


Remplacez le type de référence (ici, Loup) par Animal ou Object. Essayez avec des objets Chien, etc. Et vous verrez que tout fonctionne, excepté que vous ne pourrez pas instancier d'Object, de Felin ou de Canin !

Avant de partir en quête d'un QCM, je crois qu'une nouvelle astuce d'eclispe est disponible !

Astuce d'Eclipse

Lorsque vous créez votre classe et plus particulièrement vos classes héritées, Eclipse peut gérer la gestion des mots clé.
Il peut :
  • ajouter le mot clé extends avec la classe mère
  • ajouter les méthodes à redéfinir dans le cas d'une classe abstraite.


Voyez comment faire. Nous allons faire une classe Panthere héritée de Felin.
Une fois que vous avez inscrit le nom de votre classe, regardez plus bas : vous pouvez voir le nom de la super classe de votre nouvelle classe :


En cochant la case abstract, votre nouvelle classe sera abstraite. Et, comme vous pouvez le voir, votre classe hérite par défaut de Object. Pour changer ceci, cliquez sur Browse. Vous arrivez sur cette fenêtre ; ici, il vous suffit de mettre le nom de la classe mère, comme ceci :


Vous devez taper le nom complet de votre classe. Ici, comme nous n'avons pas encore utilisé de package, il suffit de taper Felin. En plus, Eclipse gère l'auto-complétion, ce qui signifie qu'il termine tous les noms des classes en vous proposant une liste exhaustive. Il vous suffit donc de choisir la classe mère, et de valider.
Lorsque la classe mère est dans un package, vous devez saisir le nom complet de la classe. Par exemple, si vous voulez faire une classe héritée de la classe Scanner, vous devrez commencer par saisir : java.util.Scanner.


Terminez l'étape et voici le résultat :


Vous voyez que l'héritage est bon et que les méthodes de la classe abstraite sont mises automatiquement !
Pratique, n'est-ce pas ?
Et au fait, que veut dire @Override ?

L'annotation @Override doit être utilisée lorsqu'une méthode redéfinit la méthode de la super classe.
Celle-ci existe dans la classe mère mais on redéfinit son contenu dans la classe fille.
Cependant, dans notre exemple, c'est Eclipse qui rajoute automatiquement cette annotation, mais, si vous redéfinissez une méthode d'une classe mère manuellement, vous pouvez l'ajouter vous même, tout en sachant que ceci n'est pas obligatoire !

Allez, en avant pour le topo.

Ce qu'il faut retenir

Avec les classes abstraites, vous devez vous rappelez ceci :
  • une classe est définie comme abstraite avec le mot clé abstract.
  • Une classe abstraite ne peut pas être instanciée.
  • Une classe abstraite n'est pas obligée de contenir de méthode abstraite.
  • Si une classe contient une méthode abstraite, cette classe doit alors être déclarée abstraite.
  • Une méthode abstraite n'a pas de corps.
  • Les classes abstraites sont à utiliser lorsque qu'une classe mère ne doit pas être instanciée.

Maintenant, veuillez aller à la partie suivante : Les interfaces.


0 comments to "LECON 204"

Post a Comment

About This Blog

Aller au debut de la page