Les constructeurs

 

 

1. CONSTRUCTION DES OBJETS

 

 

1.1. Introduction

 

Un objet n'est généralement pas prêt à être utilisé après sa création. Il faut donc faire appel à une procédure d'initialisation qui alimente tout ou partie de ses données membres. Cet appel risque d'être oublié. Les constructeurs apportent une solution à ce problème.

Les constructeurs seront uniquement étudiés en Java ; en pseudo-code, on pourra continuer d'utiliser une procédure d'initialisation.

 

 

1.2. Notion de constructeur

 

Il est possible de déclarer une procédure particulière chargée de l'initialisation des instances de la classe : le constructeur. Cette procédure membre porte le nom de sa classe.

A partir du moment où un constructeur est défini pour une classe, aucun objet de cette classe ne peut être créé sans appel à l'un des constructeurs définis alors que si l'on ne définit aucun constructeur (c'est ainsi que nous avons procédé jusqu'à maintenant), Java crée l'objet avec un constructeur implicite qui ne contient bien sûr aucune instruction ; dans ce cas, les données de l'objet sont initialisées à 0 si elles sont numériques, à False s’il s’agit de booléens et non initialisées s'il s'agit d'un caractère (donc le caractère sera à null qui est le code Unicode \u0000) ou d'une chaîne (donc la chaîne vaudra null).

 

Un constructeur permet d'initialiser certains champs d'un objet dès sa création, ce qui permet de garantir la cohérence d'un objet. En effet, même si vous précisez aux utilisateurs d'une classe qu'ils doivent alimenter tel ou tel champ d'un nouvel objet avant d'effectuer certaines opérations sur celui-ci, rien ne garantit qu'ils le feront effectivement, ce qui peut être source de bugs.

 

C'est le new Nomclasse (liste éventuelle de paramètres) qui provoque l'appel au constructeur.

 

Exemple :

 

Déclaration de la classe Date2 et définition du constructeur

 

public class Date2

{

            private  int jj, mm, aaaa;

            public Date2 (int  j, int  m, int  a)

{

                        jj = j;

                        mm = m;

                        aaaa = a;

}

}


 

Utilisation de la classe Date2

 

// Saisie du jour, du mois et de l'année dans j, m et a

// Création de l'objet d1 (le constructeur est appelé)

Date2 d1=new Date2 (j, m, a);       

 

// Provoque une erreur car il n'existe pas de constructeur sans paramètre

Date d2=new Date2();

 

 

1.3. Constructeur surchargé

 

Il est possible de surcharger un constructeur de la même façon que l'on peut surcharger une procédure ou une fonction.

 

Déclaration de la classe Date2 et définition de plusieurs constructeurs

 

import java.util.GregorianCalendar;

class Date2

{

private  int jj, mm, aaaa;

public Date2 (int  j, int  m, int  a)

{

                        jj = j;

                        mm = m;

                        aaaa = a;

}

public Date2 ()             // Date système

{

                        GregorianCalendar now = new GregorianCalendar();         

                        jj = now.get(GregorianCalendar.DAY_OF_MONTH);

                        mm = now.get(GregorianCalendar.MONTH)+1;

                        aaaa = now.get(GregorianCalendar.YEAR);

}

}

 

Le choix du constructeur à appliquer se fait selon la signature comme c'est le cas pour une fonction ou procédure surchargée.

On dit qu'une classe possède un constructeur par défaut s'il est possible de créer une instance de cette classe sans spécifier de paramètre lors de l'instanciation.

C'est le cas ici puisque le 2ème constructeur n'a pas de paramètre.

Quand aucun constructeur n'est défini, il y a un constructeur par défaut puisqu'il y a le constructeur implicite généré par Java.

On peut définir un constructeur par défaut sans y associer d’instructions ; dans ce cas l'objet est créé comme s'il n'y avait aucun constructeur de défini.

 


 

1.4. Appel explicite d'un constructeur

 

Le constructeur peut être appelé de manière explicite par un autre constructeur, on peut écrire :

class Eleve

{

          private String el_nom;

          private float el_oral;

          private float el_ecrit;

 

          public Eleve() //constructeur par défaut

          {

              this("toto", 0, 0);

              System.out.println("Le constructeur avec paramètres a été appelé");

          }

          public Eleve(String n, float o, float e) //constructeur avec paramètres

          {

              el_nom=n;

              el_oral=o;

              el_ecrit=e;

          }

}

L’appel du constructeur doit être la première instruction.

 

Ceci est très pratique pour donner des valeurs initiales par défaut aux données membres de l'objet (c'est à dire dans le cas où l'appelant ne les fournit pas).

 

 

1.5. Bien comprendre qu'il n'y a pas appel au constructeur lors de la simple déclaration d'une référence

 

Une référence désigne ou désignera un objet. Par abus de langage, on peut dire qu'une référence est un nom via lequel on manipule un objet.

Exemple : Date d1;    // d1 est un nom

En Java, une référence est également appelée handle (poignée, ce qui est évocateur puisque cela sert à manipuler l'objet).

L'adresse des objets ne peut être manipulée par les programmeurs, les programmeurs utilisent les handles pour manipuler les objets.

 

Tant que la référence ne désigne pas d'objet, elle vaut null.

Un objet peut être désigné par plusieurs références.

 

Divers exemples :

 

Date2 d1 = new Date2();       // Déclaration de d1

                                           // Appel au constructeur car new Date2()

 

Date2 d2 = d1;                    // Déclaration de d2

                                           // Pas d'appel au constructeur

                                           // L'objet créé précédemment est maintenant désigné par 2 références :

                                           // d1 et d2.

Remarque : si je modifie l'objet via d2, les modifications seront bien sûr visibles via d1 ou d2.


 

// Méthode statique noel qui retourne l'objet date initialisé au 25 décembre de l'année

public static Date2 noel ()

{

              Date2 res=new Date2 (25,12,2001);              // Appel au constructeur

              return res;

}

// Appel à la méthode noel

Date2 d3=Date2.noel ();            // Déclaration de d3

                                               // Pas d'appel au constructeur, d3 est une référence sur l'objet

                                               // retourné par la fonction noel

 

// Méthode qui reçoit un objet (en fait une référence sur un objet) de classe Date2

void proc (Date2 d)                                     // Pas d'appel au constructeur

{

          ...

}

Date2 d4 = new Date2();                          // Appel au constructeur

proc (d4);                                               // Transmission d'une référence sur l'objet

 

Si j'applique une méthode de mise à jour sur d dans proc, je modifie l'objet transmis. En effet, un objet est transmis par référence (c'est également le cas pour un tableau) contrairement aux données de type élémentaire qui sont toujours transmises par valeur.

 

 

2. DESTRUCTION DES OBJETS

 

La mémoire nécessaire à la mémorisation de tout nouvel objet est allouée dynamiquement à sa création. Ce n'est pas le programmeur qui gère la libération de la mémoire des objets devenus inaccessibles (un objet est inaccessible quand il n'est plus référencé par aucune variable du programme).

Cette libération est réalisée grâce au Garbage Collector (Ramasseur d'Ordure ou Ramasse miettes) fourni avec la Machine Virtuelle Java. C'est un processus qui se lance dès qu'on descend en-dessous d'un certain seuil de mémoire disponible et qui :

. récupère la mémoire occupée par les objets devenus inaccessibles,

. compacte la mémoire disponible.

 

La durée de validité d'une référence est liée à sa portée mais le programmeur peut tout de même indiquer à la Machine Virtuelle qu'une référence var1 désignant un objet n'est plus utile en la mettant à null (var1 = null;),

 

 

3. PARTICULARITES

 

3.1. Objet membre d'une classe

 

Considérons une classe représentant les jours fériés :

 

class Férié

{

              private String  nom;

              private    Date2  moment;

               public     Férié (String n, int j, int m, int a)

              {

                   moment = new Date2(j, m, a);

                   nom=n;

              }

 

              public void affich()

              {

                   System.out.println(nom);

                   moment.affich();         // On appelle la méthode affich() de la classe Date2 (méthode que

                                                    // l’on aura préalablement ajoutée à la classe Date2)

              }

}        

 

Remarque : Date2 est la classe étudiée avant (paragraphe 1.3.). On lui a juste ajouté une méthode affich().

 

Attention, si on ne définit pas de constructeur pour la classe Férié, l'objet existera mais ne comportera pas d'objet Date2. Bien sûr, on pourra créer ultérieurement l'objet Date2 de l'objet Férié. Mais dans la plupart des cas, on voudra créer l'objet imbriqué lors de la création de l'objet "contenant" donc il faudra dans ce cas définir un constructeur pour l'objet contenant ne serait-ce que pour la création de l'objet imbriqué.

Pour la classe Date2, on pourrait se contenter du constructeur par défaut, dans ce cas la date serait initialisée à plein 0.

 

 

3.2. Tableaux d'objets

 

Le constructeur peut être appelé pour construire chaque objet du tableau lors de la déclaration du tableau ou après la déclaration du tableau.

 

Exemple :

 

class Prog1tab

{            

              public static void main(String argv[])

              {

                             

                   Date2 d1=new Date2(5,2,2001);

                  

                   // Déclaration et initialisation d'un tableau d'objets Date2 (les différents cas possibles

                   // sont représentés : nouvel objet construit sans ou avec paramètres, référence sur objet

                   // existant et null)

 

                   Date2 [] tabdate ={ new Date2(), d1, new Date2(20,10,2001), null};

                                                                           

                    Date2[] tabdate2 = new Date2[3];

                   tabdate2[0] = new Date2();

                   tabdate2[1] = new Date2(10,11,2001);

                   tabdate2[2] = new Date2(20,10,2001);

                   int i;

                  

                   for ( i=0;i<3;i++)

                        tabdate[i].affich();                        

 

                    for ( i=0;i<3;i++)

                        tabdate2[i].affich();   

              }

}