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.
Le but cours de Java intermédiaire est double :
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.
Les objets (au sens java) sont mis en oeuvre à l'aide des notions suivantes :
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;
}
}
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.
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 :
Puis on peux maintenant donner une nouvelle valeur à instance2 :
instance2 = new Instance(123);
Voici le shéma mémoire associé :
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 :
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 :
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.
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();
}
}
Le modèle objet nous permet de découper un problème en sous problèmes. Nous proposons ici une illustration à travers un exemple.
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 :
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 :
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 :
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.
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 :
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.
Voici les étapes à ráliser dans l'interface NetBeans 3.2.2 pour créer la classe 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.
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.
Pour l'humain le prototype de la fonction est :
class Humain {
Tableau tableau;
public Humain(Tableau t) {
tableau =t;
}
public void joue() {
}
}
On retrouve un prototype similaire à l'humain pour l'ordinateur.
class Ordinateur {
Tableau tableau;
public Ordinateur(Tableau t) {
tableau =t;
}
public void joue() {
}
}
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 :
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.
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.
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
}
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.
|
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 :
Créer un nouveau projet comme indiqué à la section 1.4.1. Cliquer droit sur le nouveau projet et choisir ``import''
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.