Cours POO Java : comprendre la programmation orientée objet avec le langage Java
Extrait du cours :
Typage…
...
Chaque langage de programmation appartient à une “famille” de langages définissant une approche ou une méthodologie générale de programmation. Par exemple, le langage C est un langage de programmation procédurale car il suppose que le programmeur s’intéresse en priorité aux traitements que son programme devra effectuer. Un programmeur C commencera par identifier ces traitements pour écrire les fonctions qui les réalisent sur des données prises comme paramètres d’entrée.
La programmation orientée-objet (introduite par le langage SmallTalk) propose une métho-dologie centrée sur les données. Le programmeur Java va d’abord identifier un ensemble d’objets, tel que chaque objet représente un élément qui doit être utilisé ou manipulé par le programme, sous la forme d’ensembles de données. Ce n’est que dans un deuxième temps, que le programmeur va écrire les traitements, en associant chaque traitement à un objet donné. Un objet peut être vu comme une entité regroupant un ensemble de données et de méthodes (l’équivalent d’une fonction en C) de traitement.
Classe
Un objet est une variable (presque) comme les autres. Il faut notamment qu’il soit déclaré avec son type. Le type d’un objet est un type complexe (par opposition aux types primitifs entier, caractère, ...) qu’on appelle une classe.
Une classe regroupe un ensemble de données (qui peuvent être des variables primitives ou des objets) et un ensemble de méthodes de traitement de ces données et/ou de données extérieures à la classe. On parle d’encapsulation pour désigner le regroupement de données dans une classe.
Par exemple, une classe Rectangle utilisée pour instancier des objets représentant des rec-tangles, encapsule entiers : la longueur et la largeur du rectangle ainsi que la position en abscisse et en ordonnée de l’origine du rectangle (par exemple, le coin en haut à gauche). On peut alors imaginer que la classe Rectangle implémente une méthode permettant de déplacer le rectangle qui nécessite en entrée deux entiers indiquant la distance de déplacement en abscisse et en ordonnée. L’accès aux positions de l’origine du rectangle se fait directement (i.e. sans passage de paramètre) lorsque les données sont encapsulées dans la classe où est définie la méthode. Un exemple, écrit en Java, de la classe Rectangle est donné ci-dessous :
class Rectangle {
int longueur; int largeur; int origine_x; int origine_y ;
void deplace(int x, int y) {
this.origine_x = this.origine_x + x; this.origine_y = this.origine_y + y; }
int surface() {
return this.longueur * this.largeur;
}
}
Pour écrire un programme avec un langage orienté-objet, le programmeur écrit uniquement des classes correspondant aux objets de son système. Les traitements à effectuer sont programmés dans les méthodes de ces classes qui peuvent faire appel à des méthodes d’autres classes. En général, on définit une classe, dite “exécutable”, dont une méthode peut être appelée pour exécuter le programme.
Encapsulation
Lors de la conception d’un programme orienté-objet, le programmeur doit identifier les objets et les données appartenant à chaque objet mais aussi des droits d’accès qu’ont les autres objets sur ces données. L’encapsulation de données dans un objet permet de cacher ou non leur existence aux autres objets du programme. Une donnée peut être déclarée en accès :
Les différents droits d’accès utilisables en Java sont détaillés dans la section ...
Méthode constructeur
Chaque classe doit définir une ou plusieurs méthodes particulières appelées des constructeurs. Un constructeur est une méthode invoquée lors de la création d’un objet. Cette méthode, qui peut être vide, effectue les opérations nécessaires à l’initialisation d’un objet. Chaque constructeur doit avoir le même nom que la classe où il est défini et n’a aucune valeur de retour (c’est l’objet créé qui est renvoyé). Dans l’exemple précédent de la classe rectangle, le constructeur initialise la valeur des données encapsulées : class Rectangle {
...
Rectangle(int lon, int lar) { this.longueur = lon; this.largeur = lar; this.origine_x = 0; this.origine_y = 0;
}
...
}
Plusieurs constructeurs peuvent être définis s’ils acceptent des paramètres d’entrée différents.
Objet
Instanciation
Un objet est une instance (anglicisme signifiant «cas» ou «exemple ») d’une classe et est réfé-rencé par une variable ayant un état (ou valeur). Pour créer un objet, il est nécessaire de déclarer une variable dont le type est la classe à instancier, puis de faire appel à un constructeur de cette classe. L’exemple ci-dessous illustre la création d’un objet de classe Cercle en Java :
Cercle mon_rond;
mon_rond = new Cercle();
L’usage de parenthèses à l’initialisation du vecteur, montre qu’une méthode est appelée pour l’instanciation. Cette méthode est un constructeur de la classe Cercle. Si le constructeur appelé nécessite des paramètres d’entrée, ceux-ci doivent être précisés entre ces parenthèses (comme lors d’un appel classique de méthode). L’instanciation d’un objet de la classe Rectangle faisant appel au constructeur donné en exemple ci-dessous pourra s’écrire :
Rectangle mon_rectangle = new Rectangle(15,5);
ami- Remarque importante : en Java, la notion de pointeur est transparente pour le programmeur. Il faut néanmoins savoir que toute variable désignant un objet est un pointeur. Il s’ensuit alors que le passage d’objets comme paramètres d’une méthode est toujours un passage par référence. A l’inverse, le passage de variables primitives comme paramètres est toujours un passage par valeur.
Programmation orientée-objet
Accès aux variables et aux méthodes
Pour accéder à une variable associée à un objet, il faut préciser l’objet qui la contient. Le symbole ’.’ sert à séparer l’identificateur de l’objet de l’identificateur de la variable. Une copie de la longueur d’un rectangle dans un entier temp s’écrit :
int temp = mon_rectangle.longueur;
La même syntaxe est utilisée pour appeler une méthode d’un objet. Par exemple :
mon_rectangle.deplace(10,-3);
Pour qu’un tel appel soit possible, il faut que trois conditions soient remplies :
Pour référencer l’objet “courant” (celui dans lequel se situe la ligne de code), le langage Java fournit le mot-clé this. Celui-ci n’a pas besoin d’être instancié et s’utilise comme une variable désignant l’objet courant. Le mot-clé this est également utilisé pour faire appel à un constructeur de l’objet courant. Ces deux utilisations possibles de this sont illustrées dans l’exemple suivant :
class Carre {
int cote;
int origine_x; int origine_y ;
Carre(int cote, int x, int y) { this.cote = cote; this.origine_x = x; this.origine_y = y;
}
Carre(int cote) {
this(cote, 0, 0);
}
}
Introduction au langage Java
Syntaxe du langage
Le langage C a servi de base pour la syntaxe du langage Java :
a = c + c;
int a; // ce commentaire tient sur une Ligne int b;
ou
/*Ce commentaire nécessite 2 Lignes*/
int a;
Ex : mon_entier et ok4all sont des identificateurs valides mais
mon-entier et 4all ne sont pas valides pour des identificateurs.
Types de données
Types primitifs
Le tableau . liste l’ensemble des types primitifs de données de Java.
En plus de ces types primitifs, le terme void est utilisé pour spécifier le retour vide ou une absence de paramètres d’une méthode. On peut remarquer que chaque type primitif possède une classe qui encapsule un attribut du type primitif. Par exemple, la classe Integer encapsule un attribut de type int et permet ainsi d’effectuer des opérations de traitement et des manipulations qui seraient impossibles sur une simple variable de type int.
A l’inverse du langage C, Java est un langage très rigoureux sur le typage des données. Il est interdit d’affecter à une variable la valeur d’une variable d’un type différent 1 si cette seconde variable n’est pas explicitement transformée. Par exemple :
...
Tableaux et matrices
Une variable est déclarée comme un tableau dès lors que des crochets sont présents soit après son type, soit après son identificateur. Les deux syntaxes suivantes sont acceptées pour déclarer un tableau d’entiers (même si la première, non autorisée en C, est plus intuitive) :
int[] mon_tableau; int mon_tableau2[];
Un tableau a toujours une taille fixe 2 qui doit être précisée avant l’affectation de valeurs à ses indices, de la manière suivante :
int[] mon_tableau = new int[20];
De plus, la taille de ce tableau est disponible dans une variable length appartenant au tableau et accessible par mon_tableau.length. On peut également créer des matrices ou des tableaux à plusieurs dimensions en multipliant les crochets (ex : int[][] ma_matrice;). À l’instar du C, on accède aux éléments d’un tableau en précisant un indice entre crochets (mon_tableau[3] est le quatrième entier du tableau) et un tableau de taille n stocke ses éléments à des indices allant de O à n-1.
. pour utiliser des ensembles à taille variable, la classe java.util.Vector est très utile
Opérateurs
Chaînes de caractères
Les chaînes de caractères ne sont pas considérées en Java comme un type primitif ou comme un tableau. On utilise une classe particulière, nommée String, fournie dans le package java.lang. Les variables de type String ont les caractéristiques suivantes :
String s1 = ”hello”;
String s2 = ”world”;
String s3 = s1 + ” ” + s2 ;
//Après ces instructions s3 vaut ”hello world”
String s = new String(); //pour une chaine vide String s2 = new String(”hello world”);
// pour une chaîne de valeur ”hello world”
2.2 Opérateurs
Une liste des opérateurs disponibles en Java est présentée par ordre de priorité décroissante dans le tableau 2.2.
2.3 Structures de contrôle
Les structures de contrôle permettent d’exécuter un bloc d’instructions soit plusieurs fois (instructions itératives) soit selon la valeur d’une expression (instructions conditionnelles ou de choix multiple). Dans tous ces cas, un bloc d’instruction est
2.3.1 Instructions conditionnelles
Syntaxe :
if () [else ]
ou
?:
doit renvoyer une valeur booléenne. Si celle-ci est vraie c’est (resp. ) qui est exécuté sinon (resp. ) est exécuté. La partie else est facultative.
Instructions itératives
Les instruction itératives permettent d’exécuter plusieurs fois un bloc d’instructions, et ce, jusqu’à ce qu’une condition donnée soit fausse. Les trois types d’instruction itératives sont les suivantes :
TantQue...Faire... L’exécution de cette instruction suit les étapes suivantes :
Structures de contrôle
Syntaxe :
while ()
Exemple:
while (a != b) a++;
Faire...TantQue... L’exécution de cette instruction suit les étapes suivantes :
Syntaxe :
do while ();
Exemple:
do a++
while (a != b);
Pour...Faire Cette boucle est constituée de trois parties : (i) une initialisation (la déclaration de variables locales à la boucle est autorisée dans cette partie) ; (ii) une condition d’arrêt; (iii) un ensemble d’instructions à exécuter après chaque itération (chacune de ces instructions est séparée par une virgule). L’exécution de cette instruction suit les étapes suivantes :
Syntaxe :
for (;;<instr_post_itération>)
Exemple:
for (int i = 0, j = 49; (i < 25) && (j >= 25); i++, j--) { if (tab[i] > tab[j]) {
int tampon = tab[j];
D Question : que se passe-t-il si le break situé après le case ’c’ est omis ?
Syntaxe du langage
Instructions break et continue
L’instruction break est utilisée pour sortir immédiatement d’un bloc d’instructions (sans trai-ter les instructions restantes dans ce bloc). Dans le cas d’une boucle on peut également utiliser l’instruction continue avec la différence suivante :
break : l’exécution se poursuit après la boucle (comme si la condition d’arrêt devenait vraie) ; continue : l’exécution du bloc est arrêtée mais pas celle de la boucle. Une nouvelle itération du bloc commence si la condition d’arrêt est toujours vraie.
Exemple:
for (int i = 0, j = 0; i < 100 ; i++) {
if (i > tab.length) {
break;
}
if (tab[i] == null) {
continue;
}
tab2[j] = tab[i];
j++;
}
...
Éléments de programmation Java
3.1 Premiers pas
Un programme écrit en Java consiste en un ensemble de classes représentant les éléments manipulés dans le programme et les traitements associés. L’exécution du programme commence par l’exécution d’une classe qui doit implémenter une méthode particulière “public static void main(String[] args)”. Les classes implémentant cette méthode sont appellées classes exécutables.
3.1.1 Classe HelloWorld
Une classe Java HelloWorld qui affiche la chaîne de caractères “Hello world” s’écrit :
public class HelloWorld {
public static void main(String[] args) {
System.out.println(”Hello world”);
}
}
L’exécution (après compilation) de cette classe se fait de la manière suivante :
C:\>java HelloWorld Hello world C:\>
ami- Remarque : le tableau de chaînes de caractères args qui est un paramètre d’entrée de la méthode main contient des valeurs précisées à l’exécution. Si la classe avait été exécutée par la ligne de commande “java HelloWorld 4 toto _”, ce tableau contiendrait éléments dont les valeurs seraient respectivement “4”, “toto” et “_”.
Dans ce premier programme très simple, une seule classe est utilisée. Cependant, la conception d’un programme orienté-objet nécessite, pour des problèmes plus complexes, de créer plusieurs classes et la classe exécutable ne sert souvent qu’à instancier les premiers objets. La classe exécutable suivante crée un objet en instanciant la classe Rectangle (cf. section 1..1) et affiche sa surface :
Chapitre 3. Éléments de programmation Java
public class RectangleMain {
public static void main(String[] args) {
Rectangle rect = new Rectangle(5, 10);
System.out.println(”La surface est ” + rect.surface());
}
}
ami- Remarque importante : il est obligatoire d’implémenter chaque classe publique dans un fichier séparé et il est indispensable que ce fichier ait le même nom que celui de la classe. Dans le cas précédent, deux fichiers ont ainsi été créés : RectangleMain.java et Rectangle.java.
...
3.2 Variables et méthodes
3.2.1 Visibilité des champs
Dans les exemples précédents, le mot-clé public apparaît parfois au début d’une déclaration de classe ou de méthode sans qu’il ait été expliqué jusqu’ici. Ce mot-clé autorise n’importe quel objet à utiliser la classe ou la méthode déclarée comme publique. La portée de cette autorisation dépend de l’élément à laquelle elle s’applique (voir le tableau 3.1).
Le mode public n’est, bien sûr, pas le seul type d’accès disponible en Java. Deux autres mot-clés peuvent être utilisés en plus du type d’accès par défaut : protected et private. Le tableau 3.2 récapitule ces différents types d’accès (la notion de sous-classe est expliquée dans la section ).
Variables et méthodes de classe
Dans certains cas, il est plus judicieux d’attacher une variable ou une méthode à une classe plutôt qu’aux objets instanciant cette classe. Par exemple, la classe java.lang.Integer possède une variable MAX_VALUE qui représente la plus grande valeur qui peut être affectée à un entier. Or, cette variable étant commune à tous les entiers, elle n’est pas dupliquée dans tous les objets instanciant la classe Integer mais elle est associée directement à la classe Integer. Une telle variable est appelée variable de classe. De la même manière, il existe des méthodes de classe qui sont associées directement à une classe. Pour déclarer une variable ou méthode de classe, on utilise le mot-clé static qui doit être précisé avant le type de la variable ou le type de retour de la méthode.
...
La classe Math fournit un ensemble d’outils (variables et méthodes) très utiles pour des programmes devant effectuer des opération mathématiques complexes. Dans la portion de classe reproduite ci-dessus, on peut notamment y trouver une approximation de la valeur de π et une méthode convertissant la mesure d’un angle d’une valeur en degrés en une valeur en radians. Dans le cas de cette classe, il est tout a fait inutile de créer et d’instancier un objet à partir de la classe Math. En effet, la valeur de π ou la conversion de degrés en radians ne vont pas varier suivant l’objet auquel elles sont rattachées. Ce sont des variables et des méthodes de classe qui peuvent être invoquées à partir de toute autre classe (car elles sont déclarées en accès public) de la manière suivante :
public class MathMain {
public static void main(String[] args) {
System.out.println(”pi = ” + Math.PI);
System.out.println(”90° = ” + Math.toRadians(90));
}
}
…
Héritage
Dans certaines applications, les classes utilisées ont en commun certaines variables, méthodes de traitement ou même des signatures de méthode. Avec un langage de programmation orienté-objet, on peut définir une classe à différent niveaux d’abstraction permettant ainsi de factoriser certains attributs communs à plusieurs classes. Une classe générale définit alors un ensemble d’attributs qui sont partagés par d’autres classes, dont on dira qu’elles héritent de cette classe générale.
Par exemple, les classes Carre et Rectangle peuvent partager une méthode surface() renvoyant le résultat du calcul de la surface de la figure. Plutôt que d’écrire deux fois cette méthode, on peut définir une relation d’héritage entre les classes Carre et Rectangle. Dans ce cas, seule la classe Rectangle contient le code de la méthode surface() mais celle-ci est également utilisable sur les objets de la classe Carre si elle hérite de Rectangle.
4.1 Principe de l’héritage
L’idée principale de l’héritage est d’organiser les classes de manière hiérarchique. La relation d’héritage est unidirectionnelle et, si une classe B hérite d’une classe A, on dira que B est une sous-classe de A. Cette notion de sous-classe signifie que la classe B est un cas particulier de la classe A et donc que les objets instanciant la classe B instancient également la classe A.
Prenons comme exemple des classes Carre, Rectangle et Cercle. La figure 4.1 propose une organisation hiérarchique de ces classes telle que Carre hérite de Rectangle qui hérite, ainsi que Cercle, d’une classe Forme.