Le but de ce document est de vous donner un certain nombre d’informations pratiques sur la création et l’utilisation des objets et des classes. La plupart sont des rappels.
Vincent Oberlé
Les objets sont créés avec l’opérateur new. Il faut d’abord déclarer une variable pour faire référence à cet objet. Par exemple, avec la classe Date :
Date myDate;
Ensuite, il faut créer le nouvel objet et l’affecter à la variable qui vient d’être déclarée :
myDate = new Date( );
L’opérateur new appelle le constructeur de la classe Date en lui passant éventuellement des paramètres (dans les parenthèses).
Ceci peut se faire en une ligne :
Date myDate = new Date( );
Attention, si on écrit :
Date myDate1 = new Date( );
Date myDate = myDate1;
myDate et myDate1 font référence au même objet.
Après avoir créé un objet, on peut accéder à ses variables et méthodes d’instances avec l’opérateur .
Par exemple, avec la classe suivante :
class PourEssayer {
int var;
}
on créé un objet :
PourEssayer monEssai = new PourEssayer( );
et on accède à var avec
monEssai.var
par exemple
monEssai.var = 10;
De même pour les méthodes. Si la classe PourEssayer contient la méthode void print ( ), on écrit :
monEssai.print();
Les variables de classes sont stockées dans la classe elle-même. Leurs valeurs sont les mêmes pour toutes les instances de cette classe (tous les objets créés à partir de cette classe.
Les variables de classe sont définies avec le mot clé static placé devant la variable.
Par exemple :
class PourEssayer {
static int var;
}
puis
PourEssayer monPremierEssai = new PourEssayer( ); PourEssayer monSecondEssai = new PourEssayer( );
ensuite
monPremierEssai.var = 3;
Si on affiche alors monSecondEssai.var, on aura 3.
On peut aussi déclarer une méthode static, ce qui permet de stocker des méthodes qui n’opèrent pas sur des objets concrets. Dans la classe Math, il existe par exemple la méthode void cos ( ). On l’appelle avec :
Math.cos( )
Vous remarquerez qu’on met le nom de la classe avant le point et non pas une instance de cette classe.
L’héritage permet de créer une nouvelle classe avec une classe de base qui existe déjà en ajoutant des éléments à cette classe de base.
Soit la classe Rectangle. On crée la classe RectangleColoré qui hérite (qui dérive) de la classe Rectangle en écrivant :
class RectangleColoré extends Rectangle {
}
Le rectangle coloré reste toujours encore un rectangle. On peut donc utiliser
la classe RectangleColoré comme on utilisait la classe Rectangle.
Si Rectangle contient par exemple une méthode print,
on peut très bien utiliser print sur une instance de la classe RectangleColoré.
Mais l’intérêt est d’ajouter de nouvelles choses à RectangleColoré.
Par exemple, on va rajouter un variable couleur à RectangleColoré pour représenter la couleur :
class RectangleColoré extends Rectangle {
int couleur;
}
Si on suppose toujours que Rectangle contient une méthode print, cette méthode appliquée à une instance de RectangleColoré nous affichera peut-être des informations sur le rectangle, mais elle ne nous donnera rien sur la couleur du rectangle. Prenons par exemple la méthode print suivante de Rectangle :
void print ( ) {
System.out.println("Mon périmètre est " + perimetre);
}
où perimetre est une variable d’instance de Rectangle.
Pour pouvoir afficher la couleur des rectangles colorés, il est nécessaire de créer une méthode printRectangleColoré dans RectangleColoré, ou alors de redéfinir print dans RectangleColoré. C’est la méthode suivante :
void print ( ) {
System.out.println("Mon périmètre est " + perimetre);
System.out.println("Ma couleur est " + couleur);
}
Si rectangleBleu est un objet de RectangleColoré,
rectangleBleu.print();
appellera bien la méthode print de RectangleColoré.
Il est aussi possible de rappeler print de la classe Rectangle lorsque l’on redéfinit print dans RectangleColoré (pour ne pas avoir à réécrire le code) avec le mot clé super. La méthode print de RectangleColoré sera alors :
void print ( ) {
super.print( );
System.out.println("Ma couleur est " + couleur);
}
Un constructeur est une méthode particulière qui est appelé lors de la création de l’objet (avec new). Il permet notamment d’initialiser correctement les variables, mais il peut avoir beaucoup d’autres fonctionnalités.
Sa syntaxe est la suivante. Pour la classe Essai, son constructeur sera :
Essai (paramètres) {
}
Le constructeur a le même nom que la classe. Il n’a pas de valeur de retour (même pas void).
On peut lui passer des paramètres. Si on a par exemple le constructeur suivant :
Essai (int x) {
// on fait quelque chose
}
la création d’une instance de Essai se fera avec :
Essai monEssai = new Essai (4);
Avec l’héritage, lorsqu’on crée une instance d’une classe qui dérive d’une autre, c’est d’abord le constructeur de la classe de base qui est appelé, puis celui de la classe dérivée. Lorsqu’on crée un constructeur d’une classe qui dérive d’une autre classe, il est possible de passer des paramètres au constructeur de la classe de base en utilisant super. Par exemple, si EssaiSpecial hérite de la classe Essai précédente, son constructeur pourra être :
EssaiSpecial (int x, int y) {
super (x);
// on fait quelque chose
}
On notera que super(x); doit se mettre en début de constructeur.
Il existe en Java des conversions entre les types de bases. Ainsi, la conversion int vers double est implicite. Par exemple :
int x = 4; double y = x;
L’inverse par contre doit être explicité :
double y = 3.5; int x = (int) y;
La conversion entre objets existe aussi, mais elle est plus limitée. Transformer une chaîne de caractère en un bouton n’est ainsi pas possible, à moins de définir une méthode spécifique. Les seules conversions sui existent sont celles entre les classes liées par l’héritage. Si PommeVerte est une classe qui dérive de Pomme, il est possible d’affecter à une variable de type PommeVerte un objet de type Pomme puisqu’une pomme verte reste toujours une pomme. Par contre, l’inverse doit être explicité (on a une perte d’informations). Par exemple :
Pomme p; PommeVerte pv; // on affecte une Pomme a pv pv = new Pomme ( ); // la conversion doit ici être explicitée pv = new PommeVerte ( ); p = (Pomme) pv;
Vous avez peut-être remarqué des mots tels que public, private ou encore final devant la déclaration de classes, de variables ou de méthodes. Ce sont ce qu’on appellent des modificateurs. Nous avons déjà vu le modificateur static. Nous allons en voir quelques autres.
Il est possible de choisir la "visibilité" des classes, des méthodes et des variables afin de créer des classes qui ne pourront que être utilisée de façon propre. Vous pourrez ainsi créer des classes qui feront une certaine tâche et l’utilisateur de votre classe n’aura pas accès l’implémentation de votre classe. Il ne sera notamment pas tenté de court-circuiter vos méthodes pour toucher directement aux variables puisque cela lui sera interdit.
Il existe 4 niveaux de visibilités pour les variables et les méthodes d’une classe :
En général, tout ce qui concerne l’implémentation des classes, et notamment les variables, sont déclarées private. Cela permet de cacher l’implémentation à l’utilisateur et de protéger les variables. C’est le principe d’encapsulation des données.
Lors de l’héritage, il est possible de changer le niveau de protection, mais uniquement dans le sens moins public vers plus public.
Voici un exemple qui illustre ce concept :
class Essai {
private int donnee;
public int get ( ) {
return donnee;
}
public void set (int x) {
donnee = x;
}
}
Ensuite dans une autre classe, si on a une instance monEssai de Essai, il ne sera pas possible par exemple de modifier donnee en écrivant :
monEssai.donnee = 3;
Il faudra écrire :
monEssai.set(3);
Ceci peut sembler très contraignant, mais si vous décidez de changer complètement l’implémentation de votre classe en modifiant par exemple la façon dont vous stocker les données, il ne sera pas nécessaires de modifier tous les programmes utilisant votre classe puisque ceux-ci n’utilisent pas directement les données.
Remarques :
Le modificateur final permet notamment au compilateur d’effectuer des optimisations. Il est très polyvalent.
Pour déclarer des constantes en Java, il faut les déclarer final. Par exemple :
final int UNE_CONSTANTE = 4;
Il est aussi intéressant de déclarer les constantes en général public et surtout static pour y avoir accès sans qu’une instance de la classe soit nécessaire. Ce qui donne :
public static final int UNE_CONSTANTE = 4;
En général, l’héritage suit la règle suivante : les sous-classes sont plus concrètes et spécifiques que les classes dont elles dérivent qui sont plus générales et abstraites. En POO (Programmation Orientée Objet), on conçoit ainsi souvent des classes dont la seule raison d’être est d’agir comme un ensemble commun de méthodes et de variables pour des sous-classes. Par exemple, une classe Vehicule ne servirait que comme dénominateur commun entre les classes Voiture et Velo.
Il existe en Java un modificateur spécial pour ce concept : abstract. Une classe déclarée abstract est une classe normale, mais il est interdit d’en créer des instances (des objets). Elles servent donc de modèle à des sous classes.
Cela devient plus intéressant lorsque vous déclarez des méthodes abstract. En effet, une méthode abstract ne nécessite pas d’implémentation. Ce sont les sous-classes qui doivent fournir l’implémentation en redéfinissant cette méthode. C’est une obligation : une sous-classe d’une classe abstract doit redéfinir toutes les méthodes abstract de la classe de base.
Concrètement, vous pourriez utilisez la classe abstraite Vehicule pour développer une classe ListeVehicule qui utiliserait un certain nombre de méthodes de Vehicule et laisserait ensuite aux utilisateurs de ListeVehicule le soin de fournir les différentes classes dérivées de Vehicule dont ils ont besoin.
Grâce aux interfaces, il est possible d’élaborer des conceptions encore plus "pures".