Précédent Remonter Suivant

Chapitre 5  Composants d'une classe

Ce chapitre est consacré à l'organisation générale d'un programme en Java, car jusqu'ici nous nous sommes plutôt intéressés aux différentes instructions de base.

On peut considérer dans un premier temps les classes comme un moyen de regrouper plusieurs fonctions qui ont trait à un sujet commun. Par exemple on peut constituer une classe qui contient des fonctions sur les points et droites du plan, une autre pour manipuler des segments, une troisième pour les polygones etc. Cela donne une structure modulaire, plus agréable à lire, pour les programmes.

En fait une classe c'est bien plus qu'un regroupement de fonctions. En effet, on peut y placer des constantes utiles pour toutes les fonctions de la classe et qui ne seront pas modifiées par celles-ci, des variables partagées par ces fonctions et auxquelles elles pourront affecter une nouvelle valeur. Mais ce n'est pas tout, une classe permet aussi de définir un type que des variables pourront prendre, ce type peut contenir des enregistrements composés de champs. Un point un peu délicat est la construction d'un objet de la classe qui se fait à l'aide de constructeurs.

5.1  Constantes et variables globales

Une constante se déclare par
static final int promotion = 2004;
elle ne pourra pas être modifiée par les fonctions de la classe.

Les variables globales sont déclarées par
static int nbeleves;
On peut déclarer des variables globales de n'importe quel type, toutefois c'est en général des entiers qu'on utilise pour compter.

5.2  Les classes pour définir des enregistrements

Une structure, ou un type produit permet de travailler avec des variables qui possèdent plusieurs champs. Ceci est l'équivalent de l'opération mathématique du produit cartésien de deux ou plusieurs ensembles. Les déclarations se font comme pour les variables globales sans indiquer static avant le type. Ainsi on peut définir des points du plan par leurs coordonnées:
public class Point{
    float abs;
    float ord;
}
Par la suite pour accéder à la valeur d'un des champs on utilise le symbole . après le nom de la variable. Par exemple:
    x.abs = 1; x.ord = -1;

5.3  Constructeurs

Quand on fabrique une classe, il est indispensable de donner un moyen de construire les objets de cette classe. En effet, une déclaration du type
    Point x;
déclare la variable x, mais ne l'affecte pas avec la valeur d'un objet, mais avec la valeur spéciale null, dont le rôle est analogue à l'ensemble vide en mathématiques. C'est un mot réservé commun à toutes les classes.

Par défaut, chaque classe est équipée d'un constructeur implicite. Dans l'exemple précédent de la classe Point, on crée des points à l'aide de la syntaxe :
    Point x = new Point();

    x.abs = 1;
    x.ord = -1;
C'est là l'usage que nous recommandons dans ce cours.

Les tableaux que nous avons présentés au chapitre 4 partagent beaucoup de propriétés avec les objets. L'utilisation de new provoque la réservation de place en mémoire, comme pour un tableau. Ainsi:
    Point x;
    
    x.abs = 0;
provoquera ou une erreur à l'exécution, ou plus souvent à la compilation.

5.4  Méthodes de classe et méthodes d'objet

On n'utilise pratiquement que des méthodes de classe dans ce cours. Comme Monsieur Jourdain, une méthode de classe n'est rien d'autre que l'expression noble pour fonction, comme nous l'avons utilisé jusqu'à présent. Une telle méthode est associée à la classe dans laquelle elle est définie. D'un point de vue syntaxique, on déclare une telle fonction en la faisant précéder du mot réservé static.

Il existe également des méthodes d'objet, qui sont associées à un objet de la classe. L'appel se fait alors par NomObjet.NomFonction. Dans la description d'une méthode non statique on fait référence à l'objet qui a servi à l'appel par le nom this.

Un exemple complet de classe est donné ci-dessous avec les différentes possibilités de méthodes.

Exemple : les points du plan Commençons par les premières déclarations:
class PointEntier{
    int abs, ord;

    PointEntier(int a, int b){
        this.abs = a;
        this.ord = b;
    }
}
Ici, on a fabriqué un constructeur explicite pour une instance de la classe. Noter la syntaxe, qui fait de PointEntier une méthode d'objet, sans l'indication static. Quand on écrit:
    static final PointEntier origine = new PointEntier(0, 0);
on crée un objet origine dont les deux champs abs et ord seront affectés des valeurs 0 et 0. Le mot clef this permet de faire référence à l'objet en cours de création.

Remarque: quand on déclare un constructeur explicite, on perd automatiquement le constructeur implicite. Dans l'exemple précédent, un appel:
     PointEntier p = new PointEntier();
provoquera une erreur à la compilation.

Passons maintenant à l'affichage d'un objet. On peut écrire:
    public void afficher(){
        System.out.println(" Point de coordonnées "
                            + this.abs +"  " +  this.ord);
    }
qui sera utilisé par exemple dans
    origine.afficher();
On a utilisé la méthode d'objet pour afficher origine. Il existe une alternative, avec la méthode toString(), définie par:
    public String toString(){
        return "(" + this.abs + ", " + this.ord + ")";
    }
Elle permet d'afficher facilement un objet de type PointEntier. En effet, si l'on veut afficher le point p, il suffit alors d'utiliser l'instruction:
    System.out.print(p);
et cela affichera le point sous forme d'un couple (x, y).

Terminons par quelques fonctions complémentaires:
    public PointEntier oppose(){
        return new PointEntier(- this.abs, - this.ord);
    }

    public static boolean sontAlignes(PointEntier p, 
                                      PointEntier q, 
                                      PointEntier r){
        int u = (p.abs - q.abs) * (p.ord - r.ord) 
                - (p.abs - r.abs) * (p.ord - q.ord);
        return (u == 0);
    }
}
Insistons sur le point suivant: nous avons donné les différentes syntaxes de création et d'utilisation des méthodes d'objets pour être capable de comprendre les divers programmes que nous pourrions être à même d'utiliser, voire des descriptions des classes prédéfinies comme la classe String. Dans la suite de ce cours, nous ne demanderons pas d'en écrire. Par exemple, la procédure d'affichage d'un objet sera plutôt:
    public static void afficher(PointEntier p){
        System.out.print("("+p.abs+", "+p.ord+")");
        return; 
    }

5.5  Utiliser plusieurs classes

Lorsque l'on utilise une classe dans une autre classe, on doit faire précéder les noms des fonctions du nom de la première classe suivie d'un point.
class Exemple{    
    public static void main(String[] args){
        PointEntier p = new PointEntier(3,5);
        PointEntier q = p.oppose();
        PointEntier r = PointEntier.origine;
        boolean res = PointEntier.sontAlignes(p,q,r);

        p.afficher();
        q.afficher();
        r.afficher();
        System.out.println(res);
        return;
    }
}

5.6  Public et private

Nous avons déjà rencontré le mot réservé public qui permet par exemple à java de lancer un programme dans sa syntaxe immuable:
    public static void main(String[] args){...}
On doit garder en mémoire que public désigne les méthodes, champs, ou constantes qui doivent être visibles de l'extérieur de la classe. C'est le cas de la méthode afficher de la classe PointEntier décrite ci-dessus, ainsi que des méthodes toString. Elles pourront donc être appelées d'une autre classe, ici de la classe Exemple.

Quand on ne souhaite pas permettre un appel de ce type, on déclare alors une méthode avec le mot réservé private. Cela permet par exemple de protéger certaines variables ou constantes qui ne doivent pas être connues de l'extérieur, ou bien encore de forcer l'accès aux champs d'un objet en passant par des méthodes publiques, et non par les champs eux-mêmes. On en verra un exemple avec le cas des String ci-dessous.

5.7  Un exemple de classe prédéfinie: la classe String

5.7.1  Propriétés

Une chaîne de caractères est une suite de symboles que l'on peut taper sur un clavier ou lire sur un écran. La déclaration d'une variable susceptible de contenir une chaîne de caractères se fait par
  String u;
Un point important est que l'on ne peut pas modifier une chaîne de caractères, on dit qu'elle est non mutable. On peut par contre l'afficher, la recopier, accéder à la valeur d'un des caractères et effectuer un certain nombre d'opérations comme la concaténation, l'obtention d'un facteur, on peut aussi vérifier l'égalité de deux chaînes de caractères.

La façon la plus simple de créer une chaîne est d'utiliser des constantes comme:
  String s = "123";
On peut également concaténer des chaînes, ce qui est très facile à l'aide de l'opérateur + qui est surchargé:
  String s = "123" + "x" + "[]";
On peut également fabriquer une chaîne à partir de variables:
  int i = 3;
  String s = "La variable i vaut " + i;
qui permettra un affichage agréable en cours de programme. Comment comprendre cette syntaxe? Face à une telle demande, le compilateur va convertir la valeur de la variable i sous forme de chaîne de caractères qui sera ensuite concaténée à la chaîne constante. Dans un cas plus général, une expression telle que:
  MonObjet o;
  String s = "Voici mon objet : " + o;
donnera le résultat attendu si une méthode d'objet toString est disponible pour la classe MonObjet. Sinon, l'adresse de o en mémoire est affichée (comme pour les tableaux). On trouvera un exemple commenté au chapitre 10.

Voici d'autres exemples :
  String v = new String(u);
recopie la chaîne u dans la chaîne v.
  int l = u.length();
donne la longueur de la chaîne u. Noter que length est une fonction sur les chaînes de caractères, et que par contre c'est une valeur associée à un tableau; ceci explique la différence d'écriture: les parenthèses pour la fonction sur les chaînes de caractères sont absentes dans le cas des tableaux.
  char x = u.charAt(i);
donne à x la valeur du i-ème caractère de la chaîne u, noter que le premier caractère s'obtient par u.charAt(0).

On peut simuler le comportement de la classe String de la façon suivante, ce qui donne un exemple d'utilisation de private:
class Chaine{
    private char[] s;

    // s.length()
    int longueur(){
        return s.length;
    }

    // s.charAt(i)
    char caractere(int i){
        return s[i];
    }

    static Chaine creer(char[] t){
        Chaine tmp = new Chaine();

        tmp.s = new char[t.length];
        for(int i = 0; i < t.length; i++)
            tmp.s[i] = t[i];
        return tmp;
    }
}

class TestChaine{
    public static void main(String[] args){
        char[] t = {'a', 'b', 'c'};
        Chaine str = Chaine.creer(t);

        System.out.println(str.s[0]); // erreur
    }
}

Ainsi, on sait accéder au i-ième caractère en lecture, mais il n'y a aucun moyen d'y accéder en écriture.
  u.compareTo(v);
a pour résultat un nombre entier négatif si u précède v dans l'ordre lexicographique (celui du dictionnaire), 0 si les chaînes u et v sont égales et un nombre positif si v précède u.
  w = u.concat(v); // équivalent de w = u + v;
construit une nouvelle chaîne obtenue par concaténation de u suivie de v. Noter que v.concat(u) est une chaîne différente de la précédente.

5.7.2  Arguments de main

La procédure main qui figure dans tout programme que l'on souhaite exécuter peut avoir un paramètre de type tableau de chaînes de caractères. On déclare alors la procédure par
  public static void main(String[] args)
Pour comprendre l'intérêt de tels paramètres, supposons que la procédure main se trouve à l'intérieur d'un programme qui commence par Class Classex.

On peut alors utiliser les valeurs et variables args.length, args[0], args[1], ...à l'intérieur de la procédure main.

Celles-ci correspondent aux chaînes de caractères qui suivent java Classex lorsque l'utilisateur demande d'exécuter son programme.

Par exemple si on a écrit une procédure main:
  void main(String[] args){
      for(int i = args.length -1; i >= 0 ; i--)
          System.out.print(args[i] + "  ");
      System.out.println();
  }
et qu'une fois celle-ci compilée on demande l'exécution par
     java Classex marquise d'amour me faites mourir
on obtient comme résultat
  mourir faites me d'amour marquise
Noter que l'on peut transformer une chaîne de caractères u composée de chiffres décimaux en un entier par la fonction Integer.parseInt() comme dans le programme suivant:
class Additionner{

  public static void main(String[] args){
      if(args.length != 2) 
          System.out.println("mauvais nombre d'arguments");
      else{
          int s = Integer.parseInt(args[0]);

          s += Integer.parseInt(args[1]); 
          System.out.println (s);
      }
  }
}
On peut alors demander
java Additionner 1047 957
l'interpréteur répond:
2004

5.8  Les objets comme arguments de fonction

Le même phénomène déjà décrit pour les tableaux à la section 4.4 se produit pour les objets, ce que l'on voit avec l'exemple qui suit:
class Abscisse{
    int x;

    static void f(Abscisse a){
        a.x = 2;
        return;
    }

    public static void main(String[] args){
        Abscisse a = new Abscisse();

        a.x = -1;
        f(a);
        System.out.println("a="+a.x);
        return;
    }
}

La réponse est a=2 et non a=-1. Nous ne recommandons pas ce type de programmation dans le cours, quoique nous le tolérions et l'utilisions parfois pour alléger.


Précédent Remonter Suivant