Déclaration basique d'une classe


Le principe de base de la programmation objet est la représentation de tout concept sous la forme d'une classe, c'est à dire par un ensemble d'attributs et de méthodes.

Les attributs décrivent les différentes propriétés du concept et contiennent des données. Ils servent donc à mémoriser l'information que l'on possède à un moment donné sur ce concept. Pour vous rattacher à ce que nous avons déjà vu (ou autrement dit à la programmation procédurale), vous pouvez voir les attributs comme un ensemble de variables de différents types.

Les méthodes, quand à elles, représentent les opérations que l'on peut effectuer sur ce concept. Ce sont à peu de chose près des sous-programmes, mis à part qu'ils sont associés à la classe représentant le concept.

Une classe peut également être vue comme une extension de la notion de type, car comme nous le verrons un peu plus loin, il est possible de déclarer des variables d'une certaine classe tout comme on déclare des variables d'un certain type. Il nous arrivera donc dans la suite du cours d'utiliser le mot type pour désigner la classe d'une variable ou son type (au sens que vous connaissez).

Dans cette première partie du cours, nous allons voir le strict minimum nécessaire pour déclarer une classe dans un programme Java. D'où le titre "Déclaration basique d'une classe".

Notion de classe

Pour introduire la notion de classe nous prendrons comme exemple le concept de voiture. Pour simplifier, nous nous intéresserons uniquement à deux propriétés d'une voiture: son immatriculation et son compteur. Ces deux propriétés seront représentées dans la classe Voiture par deux attributs:

Nous définirons trois méthodes pour cette classe:

Le schéma suivant résume très sommairement la représentation de la classe:

Voiture1.jpg, 36kB

Déclaration d'une classe

Syntaxe

Voilà comment la classe Voiture pourrait être déclarée en Java (pour ne pas tout de suite rentrer dans les détails, j'ai ommis le code figurant dans le corps des méthodes):

 class Voiture
    {
        int  Compteur;
        String Immat ;  

        void Rouler (int Km) {  
        ... }

        boolean ChangerPneu () { 
        ... }        
    
        void AfficherAttributs() {
         ... }
    }

Le principe est assez simple: le mot clé class est suivi du nom de la classe. Ensuite, on a entre accolades la déclaration des attributs et des méthodes de la classe. Je désignerais par la suite cette partie entre accolades comme l'intérieur de la classe et par opposition, tout ce qui se situe en dehors de ces accolades comme l'extérieur de la classe.

Vous constaterez que les attributs se déclarent comme des variables et les méthodes, comme des sous-programmes.

Celles qui commencent par void se comportent comme des procédures et les autres comme des fonctions. Dans notre exemple, Rouler et AfficherAttributs sont des méthodes-procédures alors que ChangerPneu est une méthode-fonction qui retourne un résultat de type booléen.

Le Paramètre formel implicite

Apparement la déclaration d'une méthode diffère peu de celle d'un sous-programme. Mais, il y a une différence cachée: toutes les méthodes de la classe, ont un paramètre formel implicite (c'est à dire un paramètre qui n'est pas déclaré). Ce paramètre invisible est de même type que la classe.

Pour comparer avec la programmation procédurale, il faudrait imaginer à la place des trois méthodes précédentes, les trois sous-programmes suivants:

        void Rouler (Voiture v, int Km) {  
        ... }

        boolean ChangerPneu (Voiture v ) { 
        ... }        
    
        void AfficherAttributs(Voiture v) {
         ... } 

Ici nous avons rendu visible le paramètre formel implicite: il s'agit du paramètre v de type Voiture. Dans la déclaration réelle des méthodes, il n'apparait pas dans la liste des paramètres, mais il existe belle et bien.

Ou faut-il déclarer une classe ?

Pour l'instant nous nous contenterons de déclarer nos classes dans la zone de déclaration. Mais sachez que ce n'est pas l'endroit habituel et recommandé pour déclarer une classe. En effet, la déclaration d'une classe figure en général dans un fichier séparé, de même nom que la classe. Ce point sera abordé ultérieurement.

Les objets et l'instanciation

En programmation objet, il y a les classes et les objets. Pour comprendre intuitivement la différence entre classe et objet, vous pouvez comparer une classe à un moule à gateau et les objets à différents gateaux réalisés à l'aide de ce moule.

Dès lors qu'une classe a été déclarée, le moule existe et il devient possible de créer des objets à l'aide de cette classe. Ce procédé est appelé l'instanciation. Chaque objet créé à partir d'une classe est une instance de cette classe.

Reprenons l'exemple de notre classe Voiture. La déclaration de cette classe est comparable à celle d'un nouveau type de variable dénommé Voiture. Nous pouvons donc déclarer une variable de ce "type", appelons la LaVoiture. Voici la déclaration de cette variable:

 Voiture LaVoiture ;

A priori, il n'y a aucune différence avec la déclaration d'une variable d'un type donnée. Mais l'apparence est trompeuse !

On pourrait croire que nous avons ainsi déclaré un objet de la classe Voiture. En réalité, nous n'avons déclaré qu'une variable ( LaVoiture ) susceptible de contenir l'adresse d'un objet de la classe Voiture encore inexistant à ce stade. En programmation objet, on dit que l'on a déclaré une référence à un objet.

Pour ne pas trop allourdir la rédaction de ce cours, nous utiliserons souvent le terme objet pour désigner une référence à un objet. Nous dirons par exemple que la déclaration précédente est la déclaration de l'objet LaVoiture, alors que pour être rigoureux, il faudrait dire que c'est la déclaration d'une référence d'objet de la classe Voiture.

Pour créer un objet (dans le sens rigoureux du terme) de la classe Voiture, il est nécessaire d'instancier cette classe. Cette opération a pour effet de réserver la place mémoire nécessaire à stocker les attributs de l'objet dans une zone spéciale de la mémoire appelée le tas (ou heap en anglais).

Instanciation.jpg, 32kB

En Java, l'instanciation se fait par l'intermédaire de l'opérateur new suivi du nom de la classe. Cette opérateur retourne l'adresse de la zone mémoire allouée au nouvel objet créé.

L'instruction suivante:

  LaVoiture = new Voiture ();

a pour effet de créer un objet de la classe Voiture et de stocker son adresse dans la variable LaVoiture.

La référence null

Le mot clé null désigne une référence d'objet inexistante ou indéterminée. C'est la valeur par défaut des références d'objet. Par exemple, juste après la déclaration de LaVoiture, cette variable contiendra la valeur null, car aucune adresse effective d'objet ne lui a encore été affecté.

Utilisation du tas

Le mécanisme d'utilisation du tas que nous avons décrit précédemment est appelé allocation dynamique de mémoire. Il est antérieur à la programmation objet et existe également en programmation procédurale.

Son intéret est de pouvoir allouer de la place mémoire à la demande pendant l'exécution de programme. De cette manière, la place mémoire allouée est la taille minimale nécessaire pour représenter les données.

Manipulation d'un objet

Lorsqu'un objet a été créé, il est possible de le "manipuler" de deux manières:

Nous supposerons ici que cette manipulation se fait de "l'extérieur", c'est à dire que le code qui réalise cette manipulation ne figure pas à l'intérieur de la classe.

Manipulation par les attributs

Depuis l'extérieur de la classe, l'attribut d'un objet se note par le nom de l'objet, suivi d'un point, suivi du nom de l'attribut. Par exemple LaVoiture.Compteur, désigne l'attribut Compteur de l'objet LaVoiture.

Un attribut s'utilise comme une variable. On peut donc en particulier affecter une valeur à un attribut. Exemple:

  LaVoiture.Compteur = 10000;

met la valeur 10000 dans l'attribut compteur de l'objet LaVoiture.

Manipulation par les méthodes

Nous avions dit que les méthodes sont comparables à des sous-programmes, mais il y a quelques différences entre ces deux concepts. En particulier, l'application d'une méthode à un objet ne s'écrit pas comme un appel de sous-programme. Par exemple, pour faire rouler LaVoiture de 10km, on écrirait:

  LaVoiture.Rouler (10);

Pour appliquer une méthode à un objet, on écrit donc le nom de l'objet, suivi d'un point, suivi du nom de la méthode avec ses paramètres effectifs entre parenthèses.

En fait, l'objet auquel on applique une méthode est le paramètre effectif qui correspond à son paramètre formel implicite. En programmation procédurale, l'instruction précédente s'écrirait donc:

  Rouler (LaVoiture, 10);

Présentation du projet

Avant d'aller plus loin dans les explications, présentons le projet qui nous servira d'exemple (il s'agit du projet Voiture1 que vous trouverez dans le répertoire Exemple/Exemple-Java-ProgObjet1). Voici l'interface graphique de ce programme:

InterfaceVoiture1.jpg, 18kB

Le bouton Nouvelle Voiture créé une nouvelle instance de la classe Voiture, en lisant son immatriculation depuis le champ de texte prévu à cet effet (CT_Immat).

Le Bouton Rouler augmente le compteur par la distance indiquée par l'utilisateur (champ de texte CT_Distance), sauf si le compteur atteind ou dépasse 10000 km. Dans ce cas, le programme affiche un message indiquant que les pneus de la voiture sont usés. Dans le cas contraire, la nouvelle valeur du compteur est affichée dans le champ de texte correspondant (CT_Compteur).

Utilisation des méthodes de la classe

Avant d'entrer dans le détail de l'écriture des méthodes de la classe Voiture, voyons comment elles sont utilisées dans les différentes procédures évènementielles de l'application.

Dans la zone de déclaration, nous avons déclaré une variable globale nommée LaVoiture de "type" Voiture. Elle nous servira à mémoriser l'adresse d'une instance (unique dans ce programme) de la classe Voiture.

  Voiture LaVoiture ;
Le bouton Nouvelle Voiture

Voici le code de la procédure évènementielle associée à ce bouton:

 private void BT_CreerActionPerformed(... ) {                                         
  LaVoiture = new Voiture ();
  LaVoiture.Immat= es.Lire (CT_Immat);
  LaVoiture.Compteur=0;
  LaVoiture.AfficherAttributs();             
 }   

La première instruction créé une nouvelle instance de la classe Voiture et stocke son adresse dans la variable LaVoiture.

La deuxième lit l'immatriculation de la voiture à partir du champ de texte CT_Immat et stocke sa valeur dans l'attribut Immat de la nouvelle voiture.

La troisième initialise le compteur de la voiture à 0 et enfin, la dernière instruction affiche les attributs (compteur et immatriculation) de la voiture dans les champs de texte prévus à cet effet.

Le bouton Rouler

Voici le code de la procédure évènementielle associée à ce bouton:

  private void BT_RoulerActionPerformed(...) {                                          
    int d ;
    d = es.LireEntier (CT_Distance);
    LaVoiture.Rouler(d);            
    }  

La distance à parcourir est lue depuis le champ de texte CT_Distance et affectée à la variable locale d. On fait ensuite rouler la voiture avec cette distance en lui appliquant la méthode Rouler.

Remarquez que l'on suppose ici que l'instance de voiture a été générée, c'est à dire que l'utilisateur a cliqué sur le bouton Nouvelle Voiture avant de cliquer sur le bouton Rouler.

Dans le cas contraire on obtiendrait une erreur d'exécution, car LaVoiture ne contiendrait pas l'adresse d'une instance de voiture.

Ecriture des méthodes

La méthode AfficherAttribut

Voici le code de la méthode AfficherAttribut:

void AfficherAttributs()
  {
     es.Afficher(Immat, CT_Immat);
     es.Afficher(Compteur,CT_Compteur);
  }

Dans le corps d'une méthode, le paramètre formel implicite ne s'écrit pas.

Pour vous rattacher à la programmation procédurale, il faudrait imaginer le code suivant:

  void AfficherAttributs(Voiture v)
  {
    es.Afficher(v.Immat, CT_Immat);
    es.Afficher(v.Compteur,CT_Compteur);
  }

Autrement dit, dans le corps de la méthode AfficherAttribut, Immat représente implicitement l'attribut Immat de l'objet auquel on applique la méthode.

La méthode ChangerPneu

Voici le code de la méthode ChangerPneu:

 boolean ChangerPneu () {
            return (Compteur >= 10000);
 }

En programmation procédurale, ce serait la déclaration de fonction suivante:

 boolean ChangerPneu (Voiture v) {
            return (v.Compteur >= 10000);
 }
La méthode Rouler

Voici le code de la méthode Rouler:

void Rouler (int Km) {

  if (! ChangerPneu() )
  {
    Compteur = Compteur+Km ;
    AfficherAttributs ();
   }
   else
    es.AfficherMessage("Vos pneus sont usés !");
}

En programmation procédurale, ce serait la procédure suivante avec deux paramètres formels:

void Rouler (Voiture v, int Km) {

  if (! v.ChangerPneu() )
  {
    v.Compteur = v.Compteur+Km ;
    v.AfficherAttributs ();
   }
   else
    es.AfficherMessage("Vos pneus sont usés !");
}