|
Les notions de base |
Objets,
classes et héritage
Références
Les mots-clés de Java
Types primitifs
Structure d'un programme
Les packages
Qu'entend on par langage objet, ou plus exactement langage orienté objet ? Le propos qui suit, n'est que la nième réponse, qui complétera une littérature déjà vaste sur le sujet. Cette explication rapide vous permettra de vous rappeler le vocabulaire couramment utilisé en programmation orientée objet.
L'exemple donné décrit un cas typique de programmation (gestion de compte en banque), plus parlant que des cas qui peuvent paraître plus abstraits (les catégories de voitures, les espèces animales, etc...).
Le meilleur moyen d'expliquer la programmation objet passe en effet par l'exemple, car elle essaye de s'appuyer sur l'habitude qu'a l'homme, de classer les objets du monde qui l'entourent en catégories, sous-catégories, et ainsi de suite...Il faut faire la différence entre la programmation objet et un langage objet : Un langage objet tel que Java, Small Talk ou C++, est le moyen le plus aisé et le plus rapide de "programmer objet", mais la programmation objet est plus un style de programmation que peut respecter un programme écrit en C ou en PASCAL (plus difficilement tout de même !).
Mettons-nous donc à la place d'une banque : elle a des comptes à gérer, le votre, le mien et des milliers d'autres. Tous ces comptes ont en commun un certain nombre de caractéristiques communes que vous retrouvez sur votre relevé : un numéro et un solde, au minimum (même pas forcément une identité pour certains comptes).
Le banquier va donc créer un modèle de relevé avec les cases Numéro et Solde. L'imprimeur va en tirer des centaines d'exemplaires pour que le banquier puisse les remplir avec les informations de ses clients.
Le banquier gère aussi des comptes qui comportent d'autres informations dont il veut garder la trace. Il n'a pas besoin de créer un modèle entièrement nouveau : à partir de son premier modèle, il crée d'autres modèles de relevés : un pour les comptes de dépôts où il ajoute n cases Opération, un autre pour les comptes d'épargne où il ajoute une case Taux d'intérêts :
Figure 2. Des objets "relevés de compte"Notre banquier décide de s'informatiser, et engage un informaticien expert dans la technologie magique dont il entend parler dans tous les journaux : "les technologies objets".
Celui-ci lui explique sa démarche : "Pour un informaticien, chaque relevé que vous imprimez est un objet, chaque modèle que vous avez créé est une classe, et le premier modèle que vous avez créé est une classe dont les modèles suivants ont hérité. Les cases Numéro, Solde, Opération, Taux d'Intérêt sont des champs qui permettent de mémoriser l'état courant d'un compte et le solde se calcule grâce à une méthode".
Mais le banquier, pas dupe, lui répond : "Je ne veux pas vous acheter un dictionnaire !... Tout ça ne sont que de nouveaux mots. Quelle est la vraie différence avec d'autres technologies classiques et éprouvées ?".
Aïe, aïe, expliquer la différence sans terme trop technique et fumeux ?!? "Une classe est une entité qui forme un tout : chaque objet qui est une instance (désolé encore un nouveau mot !) d'une classe comporte un ensemble de champs qui décrivent son état ET un ensemble de méthodes qui permettent de le modifier : on appelle ceci l'encapsulation. L'héritage vous permet de créer de nouvelles classes dérivées d'anciennes dont elle garde ou modifie les caractéristiques, sans que vous ayez à retoucher vos anciennes classes. Convaincu ?" (Espérons-le...)Les liens d'héritage définis entre les différentes classes d'un modèle définissent un graphe d'héritage ou une hiérarchie de classes :
Figure 3. Graphe d'héritageCompteDepot est une classe dérivée de Compte. Compte est la "super classe" de CompteEpargne et CompteDepot, et CompteEpargne est la super classe de PEL. L'application Banque décrite au chapitre suivant s'inspire du modèle décrit ici.
La différence principale entre une structure C et une classe est évidente : on ne peut pas déclarer des méthodes (ou des fonctions) à l'intérieur du corps d'une structure C. A l'opposé, Java ne permet pas de déclarer de méthodes en dehors du corps d'une classe.
Une classe peut comporter uniquement des champs sans méthodes ; elle peut aussi n'avoir que des méthodes sans déclarer de champ.
L'héritage est différent de la composition : en C, vous pouvez créer une structure Compte et une structure CompteEpargne, utilisant la première :
typedef struct { int numero; float solde; } Compte; typedef struct { Compte compte; float tauxInterets; } CompteEpargne; ... CompteEpargne unCompte; unCompte.compte.numero = 1; /* Pour accéder au numero vous passez par le champ */ /* compte de CompteEpargne */En Java, vous pouvez utilisez la composition comme en C. Par contre, grâce à l'héritage, tous les champs et méthodes hérités sont accessibles directement comme s'ils avaient été déclarés par la classe dérivée elle-même.
De toute façon, ne confondez pas l'héritage et la composition : Bien que l'héritage soit une caractéristique d'un langage objet, il ne faut pas se précipiter pour l'utiliser. Vous utiliserez sûrement bien plus souvent la composition (en créant des classes qui sont l'assemblage de différents composants) et à part pour les classes d'applets, la plupart de vos premières classes n'hériteront pas d'autres classes.
Pour vous en convaincre, vous n'avez qu'à étudier la hiérarchie des classes de la bibliothèque Java, et vous verrez que la plupart des classes n'héritent pas les unes des autres.
L'héritage sert le plus souvent quand on veut modifier le comportement par défaut de classes existantes (par exemple, modifier le comportement de la classe Applet), ou quand vous avez besoin d'ajouter des fonctionnalités à une classe existante et que vous ne voulez pas modifier celle-ci parce qu'elle est déjà utilisée (par exemple, le compteur de temps dérive d'un afficheur digital statique).
La notion de référence est fondamentale en Java. La différence avec la notion de pointeur en C est faible, mais essentielle :
Les variables (champs, paramètres ou variables locales) en Java sont soit d'un type primitif (byte, short, int, long, float, double, char ou boolean), soit des références désignant des objets. Comme les pointeurs en C, ces références sont comparables à des adresses mémoires permettant de désigner un objet alloué dynamiquement. Un même objet peut être référencé par plusieurs variables à un moment donné.
Par contre, la comparaison s'arrête ici : en effet, en C un pointeur peut désigner un type primitif (int* par exemple), ou un autre pointeur (char** par exemple). Java lui, ne permet pas de déclarer une variable qui serait une référence sur un type primitif ou une référence sur une autre référence. Tout objet ne peut être créé que dynamiquement grâce à l'opérateur new : ClasseObjet unObjet; ne crée aucun objet en Java mais une référence sur un objet de classe ClasseObjet.
La notion de pointeur du C est remplacée par la notion de référence en Java, proche de celle du C++ mais limitée aux variables désignant des objets alloués dynamiquement.
En C/C++, on utilise souvent une convention d'écriture pour les noms de variables permettant de distinguer les variables qui sont des pointeurs et celles qui n'en sont pas,... comme par exemple :
struct Point { int x, y; }; struct Point point1, // point1 est une variable de type Point et n'est pas un pointeur *ptrPoint2; // ptrPoint2 est un pointeur sur Point int entier, // entier est une variable entière du type primitif int *ptrEntier; // ptrEntier est un pointeur sur intComme en Java il n'est possible de déclarer que des références comparables à ptrPoint2 ou des variables d'un type primitif comparables à entier, il n'est pas utile d'utiliser un qualificatif dans le nom des variables qui permet de rappeler qu'une variable est une référence. En général, on écrit directement point2.
Il ne faut pas voir la notion de référence comme une limitation de Java par rapport au C, mais plutôt comme une simplification de la programmation : La seule chose réellement perdue est l'arithmétique de pointeurs du C, par contre le gain en sécurité d'accès à la mémoire est important, car une référence ne peut avoir pour valeur que null ou l'adresse valide d'un objet.
La création d'objet et leur manipulation sont décrites au chapitre traitant de la création des classes.
Les liens définis dans le tableau indiquent les endroits où sont utilisés le mot-clé pour la première fois. Java 1.4 a introduit le mot clé assert, et Java 5.0 le mot clé enum.
Les mots-clés du C/C++ absent en Java
Les liens définis dans le tableau désignent les endroits où il est traité de la disparition de ces mots-clés.
TYPE
DESCRIPTION
VALEUR PAR DEFAUT Entier signé occupant 8 bits (valeurs de -128 à 127)
0
Entier signé occupant 16 bits (valeurs de -32768 à 32767)
0
Entier signé occupant 32 bits (valeurs de -2147483648 à 2147483647)
Le type int occupe toujours la même taille en Java : 32 bits.
0
Entier signé occupant 64 bits (valeurs de -9223372036854775808 à 9223372036854775807)
Le type long occupe 64 bits en Java contrairement à 32 bits en C.
0L
Nombre à virgule flottante occupant 32 bits (norme IEEE 754)
0.0f
Nombre à virgule flottante occupant 64 bits (norme IEEE 754)
0.0d
Caractère Unicode occupant 16 bits (valeurs littérales de '\u0000' à '\uffff' avec 4 chiffres hexadécimaux obligatoires après \u).
Les 128 premiers caractères sont les codes ASCII et se notent comme en C, entre '' ('a', '1',...). Voici la liste des caractères compris entre '\u0080' et '\u00ff', qui contient notamment les caractères accentués français :
Méfiez-vous car la plupart des éditeurs de texte ne génèrent pas à la compilation la bonne valeur Unicode pour ces caractères. Utilisez alors les valeurs hexadécimales ('\u00e9' au lieu de 'é' par exemple).
Le type char occupe 16 bits en Java contrairement à 8 bits en C. Utilisez byte pour une valeur sur 8 bits. Les valeurs littérales des caractères spéciaux sont les mêmes : '\n' pour un saut de ligne, '\t' pour une tabulation, '\'' pour le caractère ', '\"' pour le caractère ", '\\' pour le caractère \,...
'\u0000'
Booléen dont la valeur est true ou false (vrai ou faux).
Le type boolean n'existe pas en C. En Java, il est obligatoire dans certaines expressions (if (...) par exemple).
false
Les variables de type float et double peuvent prendre aussi des valeurs correspondant à l'infini positif ou négatif, ou représentant une valeur non significative. Voir les classes Float et Double.
Les valeurs littérales entières (byte, short, int et long) peuvent se noter de trois façons :
- Comme une suite de chiffres décimaux : 3443, -123,...
- Comme une suite de chiffres hexadécimaux (base 16) précédée de 0x : 0xFF, 0X12ab,...
- Comme une suite de chiffres octaux (base 8) précédée de 0 : 0123, 056,...
Chacun des types primitifs Java occupe toujours la même place mémoire quelque soit la plate-forme d'exécution. La taille d'un entier de type int est toujours de 32 bits (ou 4 octets).Les opérateurs qui s'appliquent à chacun des types primitifs sont étudiés au chapitre sur les instructions et les opérateurs.
Une valeur littérale d'un nombre à virgule flottante sans l'extension f ou d, comme par exemple 10.2 est considérée de type double.
La structure d'un programme Java est plus simple qu'en C.
Chaque fichier qui le compose, se divise en trois parties (optionnelles) :/* Début du fichier NouvelleClasse.java */ /* 1. Une éventuelle déclaration de package */ package nomPackage; /* 2. Zéro ou plusieurs import */ import nomClasse; // Importer une classe sans package import nomPackage.nomClasse; // Importer une classe d'un package import nomPackage.*; // Importer toutes les classes d'un package /* 3. Déclarations des classes et des interfaces du fichier */ public class NouvelleClasse // Une seule classe ou interface déclarée public, { // qui porte le même nom que le fichier // Corps de NouvelleClasse } class NouvelleClasse2 { // Corps de NouvelleClasse2 } interface NouvelleInterface { // Corps de NouvelleInterface } // ... /* Fin du fichier NouvelleClasse.java */Les packages sont comparables à des sous-répertoires et sont traités au paragraphe suivant.
Les classes d'un même package peuvent s'utiliser mutuellement sans avoir à utiliser une clause import : Si, par exemple, vous créez deux fichiers Classe1.java et Classe2.java déclarant respectivement les classes Classe1 et Classe2, vous pouvez utiliser directement Classe2 dans le fichier Classe1.java.
import
Les classes fournies avec le Java Development Kit ou par d'autres sources sont rangées dans des packages (ou paquets si vous préférez), comparables à des groupes rassemblant les classes par thème. Dans un fichier .java, vous devez indiquer à quels packages appartiennent les classes que vous utilisez. La clause import permet de spécifier ces packages pour chacune des classes ou pour chaque groupe de classes. Ces clauses se placent en début de fichier avant la déclaration de la première classe ou interface du fichier :
import nomClasse; // Importer une classe sans package import nomPackage.nomClasse; // Importer une classe d'un package import nomPackage.*; // Importer toutes les classes d'un packageimport est suivi soit directement du nom d'une classe, soit du nom d'un package, suivi lui-même du nom d'une classe ou d'un astérisque (*). L'astérisque permet d'importer les classes d'un package à la demande, c'est-à-dire que quand le compilateur recherchera une classe Classe1 qu'il ne connaît pas encore, il cherchera notamment dans les packages suivis d'un astérisque si Classe1 existe.
La classe nomClasse peut correspondre soit à un fichier source nomClasse.java, soit à un fichier compilé nomClasse.class, dans lequel est définie la classe public à importer.
Un package représente une arborescence indiquant au compilateur quel chemin il faut emprunter pour retrouver la classe. Par exemple, si le package est java.util, il va effectuer sa recherche dans le répertoire java/util (ou java\util sous Windows). Mais où est ce répertoire java/util ? Vous ne trouverez sûrement pas sur votre disque dur de répertoire java à la racine, et non plus dans le répertoire courant où vous écrivez vos programmes... Comme la plupart des langages, le compilateur Java utilise une variable système d'environnement indiquant l'ensemble des chemins prédéfinis à utiliser avec un package pour construire le chemin complet d'accès aux classes : Sous UNIX et Windows, cette variable s'appelle CLASSPATH. Vous pouvez aussi utiliser l'option -classpath avec les commandes javac et java, pour spécifier ce chemin.
Vous pouvez modifier cette variable pour y ajouter le chemin d'accès à d'autres bibliothèques Java ou à vos propres packages, que vous créerez (les environnements de développement plus complets permettent d'ajouter ces chemins plus simplement).
Le chemin correspondant à un package est donc un chemin relatif construit à partir du répertoire courant de compilation ou aux chemins cités dans la variable d'environnement CLASSPATH.import est optionnel dans les cas suivants :
- Quand vous voulez utiliser dans un fichier UneClasse.java des classes définies dans des fichiers situés dans le même package que celui de UneClasse.java : toutes les classes public ou non d'un même package / répertoire peuvent s'invoquer entre elles.
- Quand vous utilisez une classe du package java.lang : La clause import java.lang.*; est implicite à chaque compilation.
- Quand vous écrivez le nom d'une classe en la précédant de son package complet à chaque utilisation de celle-ci, par exemple en écrivant java.util.Date pour la classe Date du package java.util.
Pour utiliser une classe nomClasse d'un package nomPackage, vous avez donc le choix entre ces trois options :
En rassemblant les classes par groupes, les packages permettent d'organiser l'ensemble des classes et d'éviter d'éventuels conflits sur les noms des classes. En effet, si deux classes appartenant à deux packages distincts portent le même nom, il est toujours possible de les utiliser ensemble dans un même fichier .java, en les différenciant avec leur nom du package grâce à la troisième option .
Par exemple, la bibliothèque de Java 1.0 déclare la classe List dans le package java.awt, qui représente un composant graphique de liste. Dans Java 2, une autre classe List a été ajoutée mais elle appartient au package java.util. Cette classe permet de traiter un ensemble d'objets organisé sous forme de liste, et il est logique qu'elle porte ce nom. Si jamais vous avez besoin de ces deux classes dans un même fichier .java, il faut les utiliser sous la forme java.awt.List et java.util.List, ce qui permet de les différencier.
Comme les différents fournisseurs mettent à disposition leurs classes sous leurs propres packages, vous pouvez ainsi utiliser n'importe laquelle de leurs classes et créer des classes utilisant des noms existants dans vos propres packages, sans risque de conflits.
Si un package nomPackage comporte des sous-packages (par exemple nomPackage.sousPackage), la clause import nomPackage.* n'importe pas ces sous-packages. Il faut explicitement importer chacun d'eux (avec par exemple import nomPackage.sousPackage.*).
Les clauses import permettant d'énumérer la liste des packages auxquels appartiennent les classes utilisées dans un fichier .java, évitent de répéter le nom du package d'une classe à chaque utilisation de celle-ci. Mais il est tout à fait possible de ne spécifier aucun import et d'écrire chaque classe avec son package.
L'équivalent de #include est plus ou moins import.
Java ne requiert pas de déclaration externe via un fichier header .h ; les fichiers .java ou .class sont suffisants au compilateur pour résoudre les références aux types externes. Le mot-clé extern est inutile en Java.
import permet d'importer les classes définies dans d'autres packages (répertoires), mais n'est pas obligatoire pour importer entre elles les classes définies dans un même package, et notamment le package (répertoire) courant de développement.
Toutes les classes que vous importez explicitement ou implicitement (parce qu'elles sont du même package ou qu'elles sont du package java.lang), sont chargées dynamiquement une à une à l'exécution d'un programme Java, la première fois qu'elles sont utilisées.
Comme il est décrit au premier chapitre, les liens ne sont donc pas statiques comme en C, où le link rassemble dans un fichier exécutable tous les composants dont un programme a besoin pour fonctionner. Chaque classe est mémorisée dans un fichier .class qui peut être comparé à une (petite) librairie dynamique (ou DLL Dynamic Link Library).Définir un package
import permet d'importer n'importe quelle classe d'une bibliothèque, mais vous pouvez aussi créer votre propre bibliothèque, pour y rassembler par exemple un groupe de classes utilisées comme outils dans un ou plusieurs projets. Ceci se fait très simplement grâce à la clause package. Si cette clause est utilisée, elle doit être définie en tête d'un fichier .java, comme suit :
package nomPackage;Comme expliqué précédemment, le nom de package doit correspondre au chemin d'accès à la classe qui utilise la clause package.
En général, une société qui s'appelle nomSociete utilise com.nomsociete comme base pour le nom des packages des produits Java qu'elle livre, par exemple com.nomsociete.produitxxx pour le produit produitxxx. Les classes de ce package devront être enregistrées dans le sous-répertoire com/nomsociete/produitxxx.
Si dans ce sous-répertoire, vous créez une classe public Outil1 dans le fichier Outil1.java, chacune des classes désirant utiliser la classe Outil1, devra inclure la clause import com.nomsociete.produitXXX.Outil1; et le fichier Outil1.java devra définir la clause package com.nomsociete.produitxxx;.La figure symbolisant les modificateurs d'accès à des classes représente aussi un exemple simple d'utilisation des packages.
La plupart des exemples fournis dans ce manuel n'utilisent pas de package pour simplifier les programmes.
|