Plan

1 Les objets

La version électronique de ce cours est disponible à l'URL http://www.derepas.com/java_int.

La version d'Eclipse utilisée pour le cours est la 3.3.1.1.

1 Introduction

Le but cours de Java intermédiaire est double :

2 Rappel sur les objets

Cette section contient des rappels sur les objets en Java. Java est un langage orienté objet. En Java tout est pris dans ce que l'on appel des objets. Aussi est-il important de savoir décomposer un problème en une série d'objets permettant de le résoudre.

1 Classes et instances

Les objets (au sens java) sont mis en oeuvre à l'aide des notions suivantes :

2 La classe modèle de l'objet

Ainsi la classe est une sorte de modèle de l'objet. Pour cela elle va définir :

Une méthode spéciale permettant d'initialiser l'objet est nommée constructeur et ne retourne pas d'argument.

Voici un exemple : nous allons définir une classe contenant un entier, un constructeur, une méthode pour récupérer la valeur de cet entier, et une dernière pour fixer une nouvelle valeur.

/**
 * Définition d'une nouvelle classe.
 */
class ASimpleClass {
    /**
     * Champ i contenant la donnée.
     */
    int i;
    /**
     * Constructeur fixant la valeur initiale de i
     */
    ASimpleClass (int j) {
	i=j;
    }
    /**
     * Méthode renvoyant la valeur de i
     */
    int getValue() {
	return i;
    }
    /**
     * Méthode assigant une nouvelle valeur à i
     */
    void setValue(int j) {
	i=j;
    }
}

3 Créer des instances

Le plan que nous venons d'établir est bien joli, mais pour le moment rien n'existe : c'est juste un plan. Si on souhaite le réaliser il faut créer de nouvelles instances à l'aide du mot clé new.

Voici comment créer de nouvelles instances de la classe ASimpleClass :

ASimpleClass instance1 = new ASimpleClass(123);
Que s'est-il passé dans la machine ? Un espace mémoire a été réservé pour conserver l'information relative à l'instance instance1.

\scalebox{1}{\includegraphics{cours1/instance1.eps}}

De l'espace mémoire est réservé pour instance1.

Si on ne redemande pas de l'espace à la machine avec new on ne peut conserver qu'une seule instance. Ainsi on peut renommer l'instance précédente :

ASimpleClass instance2 = instance1;
Voici le shéma mémoire associé instance1 et instance2 sont en fait la même chose :
\scalebox{1}{\includegraphics{cours1/instance2.eps}}

instance1 et instance2 sont en fait la même chose.

Puis on peux maintenant donner une nouvelle valeur à instance2 :

instance2 = new Instance(123);

Voici le shéma mémoire associé :

\scalebox{1}{\includegraphics{cours1/instance2_prime.eps}}

instance1 et instance2 sont différents.

On peux maintenant modifier instance1 sans que instance2 ne soit impacté.

4 Champs statiques

Parfois on a envie qu'une donnée soit partagée entre toutes les instances d'une même classe, par exemple la valeur d'initialisation initValue dans la classe ASimpleClass2:

class ASimpleClass2 {
    int i;
    int defaultValue = 3;
    ASimpleClass2() {
	i=defaultValue;
    }
    int getValue() {
	return i;
    }
}

Le problème c'est que pour chaque instance on va stocker deux entiers : i et defaultValue. C'est illustré par le schéma ci-dessous :

\scalebox{1}{\includegraphics{cours1/asimpleclass_nostatic.eps}}

Le champ defaultValue est présent dans chaque instance.
Or si on a beaucoup d'instances on va utiliser inutilement beaucoup de mémoire en mémorisant à chaque fois la valeur de initValue qui est en fait commune à toutes les instances.

Pour ``factoriser'' le champ defaultValue il faut rajouter l'attribut static devant le type, comme ci-dessous :

class ASimpleClass3 {
    int i;
    static int defaultValue = 3;
    ASimpleClass3() {
	i=defaultValue;
    }
    int getValue() {
	return i;
    }
}
La représentation mémoire est décrite ci-dessous :
\scalebox{1}{\includegraphics{cours1/asimpleclass_static.eps}}

Le champ static defaultValue est ``mutualisé'' entre les instances.

Exemple: Quand on affiche une chaine de caractères à l'aide de la commande System.out.println("Hello World!") on appelle la méthode println sur l'objet out qui est un champ statique de la classe System. Il n'y pas besoin de manipuler d'instance de la classe System.

Si on sait que la valeur d'initialisation defaultValue sera inchangée pendant l'exécution du programme on peut de plus rajouter l'attribut final qui interdit sa modification :

    final static int defaultValue = 3;
Dès lors toute modification de defaultValue provoquera une erreur à la compilation.

5 Publique et privé

Une notion importante est celle de champs ou méthodes publiques ou privés. Une méthode ou un champ publique peut être vu ou modifié par les méthodes des classes extérieures à la classe courante. En revanche les variables privées ne peuvent être modifiées que par des méthodes de la classe courante.

Considérons une classe Voiture modélisant une voiture qui peut démarrer et s'arrêter qui comprend une boite de vitesse et un moteur. Vu de l'extérieur on ne souhaite pas nécessairement exposer le fait que la voiture comprend une boite de vistesse et un moteur, peut-être parce qu'elle pourrait contenir deux moteurs dans une nouvelle version, ou que plus tard on pense changer le type de boite de vitessse. Aussi peut-on écrire la structure suivante

class Voiture {
    BoiteDeVitesse maBoite;   // champ privé
    Moteur monMoteur;         // champ privé
    public Voiture() {        // méthode publique
        // ...
    }
    public void start() {     // méthode publique
        monMoteur.start();
    }
    public void stop() {      // méthode publique
        monMoteur.stop();
    }
}

\scalebox{1}{\includegraphics{cours1/public_and_private.eps}}

Notion de variable publique et privée.
Il faut bien comprendre que cette notion de variable publique et de variable privée est centrale dans le modèle objet. Elle permet de ``passer à l'échelle''. En effet on peut ainsi construire progressivement une hiérarchie d'objets en spécifiant à l'aide des parties publiques comment ces derniers vont être manipulés. Puis ensuite détailler comment ils vont réaliser leur tâches en écrivant les méthode privées, ce qui nous ammènera peut-être alors à créer de nouvelles classes.

3 Le modèle objet : diviser pour régner

Le modèle objet nous permet de découper un problème en sous problèmes. Nous proposons ici une illustration à travers un exemple.

1 Un exemple : Puissance 4

Nous nous proposons de faire un jeux de puissance 4, d'un humain contre la machine. Nous allons détailler la hiérarchie des objets à mettre en oeuvre pour réaliser un tel programme.

L'idée est de décomposer le jeux en ensembles ayant des responsabilités bien définies. L'idée est de procédér ``en gros'', on se réserve le droit de rajouter de nouveaux objets ou de détailler plus tard chaque objet. On peut par exemple choisir comme ensembles :

L'important est maintenant de clairement déterminer pour chaque ensemble :

2 Le tableau

Son rôle est de représenter le tableau physique pour savoir où sont les jetons.

Les données conservées sont donc l'ensemble des cases avec la position des jetons.

Les interactions avec les autres sont :

On remarque de l'on parle d'un jeton. Il serait donc bon de formaliser cette notion avec une classe.

3 Le jeton

Son rôle est de représenter un jeton physique.

La donnée conservée est simplement la couleur du jeton : rouge ou jaune.

Les interactions avec les autres sont :

4 Le joueur humain

Son rôle est de pouvoir donner à un humain la possibilité de jouer.

Il n'y a a priori pas de données conservées.

Les interactions avec les autres sont :

Il faut donc en fait quand même conserver un donnée : une référence au tableau qui stocke les jetons. Ce sera l'unique champ de la classe.

5 Le joueur informatique

Son rôle est de pouvoir permettre à un programme de jouer.

Comme dans le cas du joueur humain il faudra conserver une unique donnée : une référence au tableau qui stocke les jetons.

Les interactions avec les autres sont :

4 Création du squelette des classes

Nous avons maintenant une vue claire de la façon dont les classes vont s'agencer et quels sont leurs rôles respectifs. Nous allons pouvoir créer le squelette des classes.

1 Création de la classe Tableau dans Eclipse

Voici les étapes à ráliser dans l'interface NetBeans 3.2.2 pour créer la classe Tableau :

\scalebox{1}{\includegraphics{cours1/workbench.eps}}

Si besoin afficher le ``workbench''.

\scalebox{1}{\includegraphics{cours1/open_project.eps}}

Dans le menu ``file'' choisir ``New''puis ``Java Project''

\scalebox{1}{\includegraphics{cours1/project2.eps}}

Rentrer le nom du projet et cliquer sur terminer.

\scalebox{1}{\includegraphics{cours1/project3.eps}}

On obtient alors un écran comme ci-dessus

\scalebox{1}{\includegraphics{cours1/package1.eps}}

Cliquer droit sur le package choisir ``New'' et ``Package''

\scalebox{1}{\includegraphics{cours1/package2.eps}}

Rentrer un nom pour le package, par exemple ``cours1''

\scalebox{1}{\includegraphics{cours1/classe1.eps}}

Cliquer droit sur le projet choisir ``New'' et ``Class''

\scalebox{1}{\includegraphics{cours1/classe2.eps}}

Rentrer ici le nom de la classe ``Tableau''

2 Le tableau

Conformément aux données de la section précédente nous pouvons déclarer le prototype :

class Tableau {
    Jeton donnees[][];
    public Tableau() {
    }
    public void ajouteJeton (Jeton jeton, int colonne) {
    }
    public boolean jeuTermine() {
    }
    public void affiche() {
    }
    public Jeton quelJeton(int colonne, int rangee) {
    }
}

On constate que si l'on fait un tableaux à deux dimensions d'instances de type Jeton il faut quand même pouvoir dire qu'une case n'est pas occupée. Pour cela on peut rajouter une couleur supplémentaire que l'on appelle VIDE qui voudra dire que la case (i,j) est vide si la couleur de donnees[i][j] est VIDE.

Pour compléter les méthodes il nous faut avoir déjà définit la classe Jeton.

3 Le jeton

Nous allons coder la couleur du jeton dans un champ de type int. Si ce champ vaut 0 alors cela veut dire que la case est vide (cela correpond à la couleur VIDE). Si ce champ vaut 1 alors cela veut dire que le jeton est rouge. Si ce champ vaut 2 alors cela veut dire que le jeton break; est jaune.

Pour se souvenir de ces conventions nous allons créer les champs statiques constants VIDE, ROUGE et JAUNE pour éviter de se souvenir que ROUGE vaut 1 :

class Jeton {
    public static final int VIDE = 0;
    public static final int ROUGE = 1;
    public static final int JAUNE = 2;
    int couleur;
    public Jeton() {
	couleur = VIDE;
    }
    public Jeton(int c) {
	couleur = c;
    }
    public int donneCouleur() {
	return couleur;
    }
}
On a créé un constructeur par défaut qui met la couleur à VIDE.

4 Le joueur humain

Pour l'humain le prototype de la fonction est :

class Humain {
    Tableau tableau;
    public Humain(Tableau t) {
	tableau =t;
    }
    public void joue() {
    }
}

5 Le joueur informatique

On retrouve un prototype similaire à l'humain pour l'ordinateur.

class Ordinateur {
    Tableau tableau;
    public Ordinateur(Tableau t) {
	tableau =t;
    }
    public void joue() {
    }
}

6 Et maintenant ?

Maintenant nous avons les objets il faut encore les lier entre eux : instancier un tableau, un joueur informatique, un joueur humain.

On peut faire cela dans une classe nommée Puissance4 comportant une instance de Tableau, Humain et Ordinateur. On lui adjoindra une méthode nommée jouer pour lancer le jeu. On mettra également la méthode main dans cette classe.

class Puissance4 {
    Tableau tableau;
    Humain humain;
    Ordinateur ordinateur;
    Puissance4() {
    }
    void jouer() {
    }
    public static void main(String argv[]) {
    }
}

Une fois ces classes créées on peut les exécuter :

\scalebox{1}{\includegraphics{cours1/run1.eps}}

Sélectionner la classe Puissance4, sur le Bouton ``run'' choisir ``Run As'' puis ``Java Application''

\scalebox{1}{\includegraphics{cours1/run2.eps}}

Choisir les ressources à sauver

5 Contenu des méthodes

Nous avons déterminé l'infrastructure de nos classes. Il faut maintenant remplir le corps des méthodes. Nous allons pour cela déterminer des ``cas d'utilisation'' (use case en anglais) permettant de donner des cas d'utilisation concrêts.

1 Cas d'utilisation normal

Nous allons tout d'abord commencer par un cas simple : celui où une partie normale se déroule. Voici ci-dessous un diagramme où l'humain commence.

\scalebox{1}{\includegraphics{cours1/msc1.eps}}

Déroulement d'une partie où l'humain commence.

C'est l'occasion de remarquer que nous avons oublié de dire comment la partie finissait. Il faut en effet rajouter un test après l'ajout d'un nouveau jeton pour savoir si un joueur a gagné ou bien s'il y a match nul. Nous ajoutons donc dans Puissance4 la méthode indiquant si le jeu actuel est terminé :

    boolean jeuTermine() {  // à remplir
    }

6 Conclusion

Dans le cours nous avons procédé aux rappels sur la description des objets dans le langage Java. Nous avons mis l'accent sur l'aspect compositionnel des objet à travers les attributs public et private.

Une méthodologie générique de la manière de concevoir les classes a été présenté à travers un exemple. En voici un rappel.

  1. Déterminer approximativement un découpage en classes permettant de modéliser les données présentes.
  2. Décrire textuellement les classes : leur rôle, les données conservées, la liste des actions à réaliser.
  3. Ecrire le squelette du code Java des classes.
  4. Remplir le corps des méthodes.

7 Exercices

Ex 1.1 * Nouvelles instances Nouvelles instances *

On considère le programme Java ci-dessous :

class Ex1 {
    int i=3;

    Ex1() { }
    int getValue() { return i;}
    void setValue(int j) { i=j;}

    static boolean methode1() {
	Ex1 instance1 = new Ex1();
	Ex1 instance2 = instance1;
	instance2.i=4;
	return instance1.i==3;
    }
    static boolean methode2() {
	Ex1 instance1 = new Ex1();
	Ex1 instance2 = new Ex1();
	instance2 = instance1;
	instance2.i=4;
	return instance1.i==3;
    }
}

Q1 Que retourne la méthode Ex1.methode1. Expliquez le résulat.

Q2 Que retourne la méthode Ex1.methode2. Expliquez le résulat.

Ex 1.2 * Ordre d'initialisation Ordre d'initialisation *

On a vu que l'on pouvait utiliser des champs statiques d'une classe sans qu'une instance ne soit créée. On en déduit dont que les champs statiques doivent être initialisé avant toute exécution de méthode d'une classe. Qu'affiche alors le programme ci-dessous :

class Ex2 {
    static class MaSousClasse {
	MaSousClasse(String s) { 
	    System.out.println(s);
	}
    }
    MaSousClasse champ1 = new MaSousClasse("Champ 1");
    MaSousClasse champ2;
    static MaSousClasse champ3 = new MaSousClasse("Champ 3");

    Ex2() {
	System.out.println("Constructeur de Ex2");
	champ2 = new  MaSousClasse("Champ 2");
    }

    public static void main(String[] argv) {
	System.out.println("Execution de Ex2.main");
	Ex2 ex2 = new Ex2();
    }
}

Ex 1.3 ** Puissance 4 Puissance 4 **

Le but de l'exercice est de remplir le corps des classes présenté dans ce cours pour disposer d'un jeu de Puissance 4.

Si vous êtes à l'aise faite le vous même. Sinon suivez les instructions ci-dessous :

Q1 Téléchargez sur votre disque dur le fichier jar suivant :

http://www.derepas.com/java_int/ex_1_3.jar
Créer un nouveau projet comme indiqué à la section 1.4.1. Cliquer droit sur le nouveau projet et choisir ``import''

\scalebox{1}{\includegraphics{cours1/import1.eps}}

Choisir ``Archive File'' puis cliquer sur suivant

\scalebox{1}{\includegraphics{cours1/import2.eps}}

Rentrer le nom du fichier jar précédemment téléchargé puis cliquer sur ``Finish'', puis ``Yes to all''

Q2 Compléter la méthode joue dans la classe Ordinateur pour jouer au hasard dans une colonne non pleine. Pour tirer un entier i et j entre 0 et 6 compris au hasard en Java on peut utiliser les instructions suivantes :

        java.util.Random r = new java.util.Random();
        int i = r.nextInt(7);
        int j = r.nextInt(7);

Q3 faire de même avec la classe Humain

Q4 compléter la méthode jouer dans la classe Puissance4. Vérifier le résultat d'une partie.



2007-11-18