Home Map Index Search News Archives Links About LF
[Top Bar]
[Bottom Bar]
[Photo of the Author]
Jose M. Fernández 

Ecrivez à l'auteurAuthor e-mail
Index
Lexicalité
Types de données, variables et opérateurs
Structure de contr^ole
Classes, Méthodes, Héritage 

Java. 2ème partie

[Ilustration]

Résumé: cette série d'articles suit le modèle classique des livres sur la programmation structurée. La première partie introdusait les caractéristiques du langage Java, nous continuons maintenant avec les types de données, les variables, les structures de contr^ole, etc., jusqu'à finalement introduire les classes, sujet principal et le plus important lorsque l'on sait que les classes sont la base de ce langage de programmation. Excepté les classes, les autres sujets traités sont très similaires à de nombreux autres langages, aussi nous avons choisi de les parcourir rapidement et brièvement.


Lexicalité

Un programme Java est formellement constitué d'un ensemble de commentaires, identificateurs, séparateurs, espaces, et mots clés. Le compilateur reçoit du code écrit sous un certain format, et étend le jeu de caractères ASCII 8 bits à 16 bits, agrandissant ainsi la table de caractères pour permettre l'utilisation des caractères des langages non latin. Les espaces, les tabulations et les retours à la ligne sont ignorés par le compilateur car ils n'appartiennent pas au jeu de symboles. Cela permet un style libre pour le fichier source Java.

Les commentaires peuvent s'écrire selon trois syntaxes différentes en Java:
 

// commentaire Les caractères depuis // jusqu'à la fin de la ligne sont ignorés par le compilateur.
/* commentaire */ Tous les caractères entre /* et */  sont ignorés. Plusieurs lignes peuvent ^etre comprises ente les marques.
/** commentaire **/ Comme précédamment, mais ils devraient ^etre réservés avant les déclarations car l'outil javadoc les utilise pour générer automatiquement la documentation. 

Les identificateurs sont les noms donnés aux variables, classes ou méthodes définies par l'utilisateur . On peut utiliser n'importe quelle ch^aine de caractères (minuscule ou majuscule), y compris les chiffres et les signes dollar et souligné. Les identificateurs ne peuvent cependant pas commencer par un chiffre.

Java réserve certains caractères pour les séparateurs. Le plus rencontré est le point-virgule, mais on trouve aussi:
Symb^ole Description
() Contient la liste des paramètres à la définition et à l'appel des méthodes. Il est aussi utilisé pour changer la précédence des expressions, pour inclure des expressions dans une structure de contr^ole, et pour changer le type d'une expression.
{} Définit un bloc de code pour les classes, méthodes, ou pour déclarer localement une visibilité. Aussi utilisé pour initialiser les vecteurs.
[] Définit le type matrice, et permet d'accéder à leurs éléments.
; Séparateur de phrase.
, Sépare différents identificateurs lors des déclarations de variables, ou lors des appels de méthodes.
Aussi utilisé pour séparé les déclarations dans une boucle "for".
. Sépare les différents "package" et nom de classes. Aussi utilisé pour appliquer une méthode à une variable.

Les mots clés sont des termes réservés du langage Java. On ne peut les utiliser comme identificateurs. Voici une liste de mots clés du langage Java:
 

abstract double int super
boolean else interface switch
break extends long synchronized
byte false native this
byvalue final new threadsafe
case finally null throw
catch float package transient
char for private true
class goto protected try
const if public void
continue implements return while
default import short  
do instanceof static  

Types de données, Variables et Operateurs.

J'insiste ici sur le fait que Java est un langage fortement typé, chaque variable à un type, et chaque type est rigoureusement défini. Les compilateurs vérifient toutes les types lors des assignations, qu'elles soient explicites ou implicites (par passage de paramètre aux méthodes). les compilateurs vérifient aussi la compabilité de type pour toutes les expressions, et tous les passages de paramètres.

Lors du précédent article, nous avons indiqué que Java est compplètement orienté objet. Néanmoins, pour des raisons d'efficacité, Java définit huit types "simples" qui ne sont pas des objets.  Pour des raisons de portabilité, chaque type simple à une plage de variation définie.

Les types simples peuvent ^etre classés en quatre groupes:
GROUPE TYPE TAILLE (bits) PLAGE
Nombres entiers long 64 -9.223.372.036.854.775.808 
à
9.223.372.036.854.775.807
  int 32 -2.147.483.648 
à
2.147.483.647
  short 16 -32.768 à 37.767
  byte 8 -128 à 127
Nombres à virgule flottante float 32 3.4 e-038 à 3.4 e+038
  double 64 1.7 e-308 à 1.7 e+308
Caractères char 16 Unicode
Booléens boolean   Vrai ou faux

Variables:

Une variable est définie par un idenificateur et un type. Java permet de simultanément déclarer et initialiser les variables. Chaque variable à une visibilité, et une durée de vie. Les variables doivent ^etre déclarées avant d'^etre utilisées. Elles peuvent ^etre déclarées n'importe où dans le programme, m^eme juste avant d'^etre utilisées pour la première fois.

En général, la déclaration d'une variable ressemble à ceci:
 

type identificateur1 [=valeur1] [,identificateur2 [=valeur2]......];
            

où "type" peut ^etre un type Java de base, une classe ou m^eme une interface. Si on initialise la variable, l'expression doit ^etre du m^eme type ou un type compatible à celui spécifié à la déclaration de la variable.

Exemples:
 

int  a = 4, b, c=7;
char c;
myclass d;
            

En règle générale, une variable définie à l'intérieur d'une zone de visibilité n'est pas visible par le code extérieur (on définit la zone de visibilité par le code compris entre deux accolades {}). Une variable ne conserve pas sa valeur lorsque l'on sort de sa zone de visibilité.

La plupart des langages de programmation définissent deux catégories de visibilité, global et local. Cependant, cela s'accomode mal de l'orientation objet de Java. Dans ce modèle, les deux visibilités principales sont celles définies par la classe et par la méthode.

Conversion de type:

Bien que la théorie le nie, Java permet d'assigner une valeur d'un certain type à une variable d'un autre type. Si les types sont compatibles, une conversion automatique intervient; sinon, il est toujours possible de réaliser une conversion explicite entre deux types incompatibles. Pour réaliser une conversion automatique, il faut que: Par exemple, le type "int" est suffisamment grand pour stocker un type "byte", il n'impose donc pas de conversion explicite. Les types de nombres ne sont pas compatibles avec les type caractère ou booléens, et ces derniers ne sont pas compatibles avec le reste. Pour assigner une valeur "int" à un "byte", on utilise la conversion de type explicite, avec la syntaxe suivante:
 
(type) value

où type indique le type cible. Par exemple:
 

int a;
char b;
a=(int) b;
            

Il faut ^etre rès prudent avec la conversion de type, car elle peut provoquer une perte d'information. Par exemple:
 

int a;
double d= 125.43;
a=(int) d;
La variable a vaut 125. 
byte   b;
int i=257; 
b=(byte) i;
            
b vaut 1, résultat de la division de 257 par 256 où 256 est le maximum du type "byte".
byte b;
double d= 340.123;
b=(byte) d;
b vaut maintenant 84.

Toutes ces conversions ne posent aucun problème à la compilation ou à l'exécution.

Opérateurs:

Le vaste ensemble des opérateurs peut ^etre classé en quatre groupes: arithmétique décimale,  logique binaire, relationnel, et logique. En règle général, le fonctionnement est similaire à d'autres langages, mais nous présenterons les quelques différences.
Opérateurs arithmetiques:
Opérateur Description
+ Addition
- Soustraction
* Multiplication
/ Division
% Modulo (reste de la division)
++ Incrémentation
+= Addition puis assignation
-= Soustraction puis assignation
*= Multiplication puis assignation
/= Division puis assignation
%= Modulo puis assignation
-- Décrémentation

L'opérateur modulo peut ^etre appliqué à la fois aux entiers et aux nombres en virgule flottante. Par exemple:
 

int a=38;
double d=41.95;
int c=a%10;
double  e=d%10;

La variable c vaut 8, et a pour valeur 1.95.

Les opérateurs avec assignation sont utiles pour:

a = a + 4; equivaut à a += 4;

a = a % 2; equivaut à a %= 2;

De manière générale, les expressions telles:
var=var op expression;
peuvent ^etre remplacées par
var op= expression;

Opérateurs de logique binaire:
Les opérateurs de logique binaire s'appliquent aux types long, int, short, char, et byte modifiant leurs bits.
 
Opérateur Description
~ Négation
& ET
| OU
/\ OUExclusif
>> Décalage à droite
>>>> Décalage à droite avec insertion de zéros
<< Décalage à gauche
&= ET puis assignation
|= OU puis assignation
/\= OUExclusif puis assignation
>>= Décalage à droite puis assignation
>>>>= Décalage à droite avec insertion de zéros puis assignation
<<=  Décalage à gauche puis assignation
Opérateurs relationnels:
Ils déterminent la relation entre deux opérandes, avec pour résultat une valeur booléenne.
 
Opérateur Description
== Egal à
!= Différent de
> Supérieur à
< Inférieur à
>= Supérieur ou égal
<= Inférieur ou égal

Au contraire d'autres langages (C/C++), les valeurs booléennes sont "true" et "false", c'est à dire qu'elles ne sont pas des valeurs numériques.

Opérateurs logiques:
Ils fonctionnent avec des opérandes de type booléen, et renvoient aussi une valeur booléenne.
 
Opérateur Description
& ET
| OU
/\ OUExclusif
|| OU raccourci
&& ET raccourci
! Négation
&= ET puis assignation
|= Ou puis assignation
/\= OUExclusif puis assignation
== Egal à
!= Différent de
?: Ternaire Si-alors-sinon

Le OU raccourci prend la valeur true quand le premier opérande est vrai, quel que soit le deuxiéme opérande. Le ET raccourci prend la valeur false quand le premier opérande est faux, quel que soit le deuxiéme opérande.

La forme générale de l'opérateur ternaire est:

expession1 ? expession2 : expression3

Si expression1 est vraie, expression2 est exécutée; sinon, expression3 est exécutée.

Précédence des opérateurs:
 

Plus élevé

( ) [ ] .  
++ -- ~ !
* / %  
+ -    
>> >>>> <<  
> >= < <=
= = !=    
&      
'      
|      
&&      
||      
?:      
= Op=    
Plus faible

Structures de contr^ole du déroulement du programme

Elles peuvent ^etre classées en trois groupes: sélections, boucles et sauts.
 
Groupe Expression Description
Sélections if
if ( condition ) 
  instruction1;
else 
  instruction2; 
  Plusieurs "if"
if (condition ) 
  instruction1;
else if (condition) 
  instruction2;
else if (condition) 
  instruction3;
.
.
else 
  instructionX;
  switch
switch (expression){
  case valeur1:
    expression;
    break;
  case valeur2:
    expression;
    break;
    .
    ...
  default :
    expression;
}
Boucles while
while (condition) {
  expression;
}
  do while
do {
  expression;
} while (condition)
  for
for (initialisation, condition, réinitialisation) {
  expression;
}
Sauts break Pour sortir d'une structure "switch".

Pour sortir d'une boucle.

  continue Passe immédiatement à l'itération suivante de cette boucle.
  return Sortie explicite d'une méthode, retour au programme appelant.

Classes, Méthodes, Héritage

Java a été réellement conçu ex-nihilo, comme nous l'avons indiqué dans notre article précédent; c'est la raison pour laquelle Java propose une implémentation propre et utile d'un langage orienté objet. En conséquence, tous les programmes Java sont orientés objet. Cette série d'articles ne présente pas la programmation orientée objet. Une vaste littérature est consacrée à ce sujet, de la base jusqu'aux références les plus avancées. Mais la programmation orientée objet (OOP-Object Oriented Programming) est tellement iimportante pour appréhender Java, qu'une compréhension de base est indispensable avant de commencer à programmer dans ce langage. Nous devons utiliser certains éléments de base de cette démarche, en utilisant toujours la terminologie Java, et nous essayons toujours de définir de la manière la plus précise et la plus concise les éléments qui font de Java un langage orienté objet.

CLASSES :

C'est le coeur de Java. La classe définit la forme et la nature d'un objet, elle représente la  base de la programmation orientée objet. Un type de donnée est défini par une classe, et la classe sert à créer des objets de ce type.

La classe est un modèle pour un objet, et un objet est une instance (réalisation) d'une classe. Java n'accepte pas les fonctions ou variables globales, donc toutes les actions (méthodes) d'un programme doivent ^etre définies dans une classe.

On définit une classe en utilisant le mot réservé "class". Par exemple, le fragment de code définit une classe:
 

Class classe {
  Type variable1;
  Type variable2;
  .
  .
  ...
  type variableN;
  type methode1(liste_parametre){
    // corps de la methode
  }
  .
  .
  ..
  type methode2(liste_parametre){
    // corps de la methode
    .
  }
}

Les variables ou données définies dans une classe sont appelées variables d'instance. Les méthodes contiennent le code, et définissent comment les données d'une classe doivent ^etre utilisées.

Deux étapes sont nécessaires pour obtenir les objets d'une classe:

L'opérateur "new" réalise cette deuxième étape. Il se présente ainsi:
 
            
variable = new nom_de_la_classe();

Où "variable" est le nom de l'objet que l'on souhaite créer, et "nom_de_la_classe" est le nom de la classe instanciée. La création d'un objet peut ^etre représentée graphiquement ainsi:


 

METHODES:

La déclaration d'une méthode est réalisée de la manière suivante
type nom_de_la_methode( liste_de_parametre ) {

  // corps de la methode
}

"type" est le type de l'objet retourné par la méthode ; il peut s'agir de n'importe quel type valide, y compris des classes, ou encore la méthode peut ne pas renvoyer de valeur (type "void").

La lite des paramètres est composée de paires (type, identificateur), séparées par des virgules. Les paramètres sont des variables qui prennent la valeur des arguments passés à la méthode. Si la méthode n'a pas d'arguments, la liste est vide.

Les méthodes renvoyant une valeur se terminent par l'instruction:

            
return valeur;

où "valeur" est la valeur renvoyée.

Les méthodes fournissent à Java sa puissance et sa flexibilité, aussi nous leur consacrerons la suite de cet article.

Cependant, nous allons d'abord revoir à travers un exemple simple les concepts que nous venons d'exposer.

Nous allons créer une classe qui calcule le volume d'un parallèlipède (comme une piscine):
 

Code Commentaires
class capacite {
    double longueur;
    double largeur;
    double hauteur;
    void CalcVolume () {
        double volume ;
        volume = longueur*
                    largeur*
                    hauteur;
        System.out.println("Volume: " +
                           volume);
    }
}
Nous définissons une classe appelée "capacite", qui comporte trois variables numérique (type double): longueur, largeur et hauteur. La classe définit aussi une méthode "CalcVolume" qui permet de calculer le volume de la bo^ite. Le code source est enregistré dans le fichier "capacite.java"; à la compilation, la classe "capacite.class" est créée.

Cette classe isolée ne produit aucune action, ce n'est pas non plus une applette. Nous avons réalisé un canevas qui permet de créer des objets, instances de cette classe. Nous allons maintenant écrire un programme. Un programme est lui-m^eme une classe qui dispose d'une méthode spéciale "main", appelée lors de l'exécution depuis la ligne de commande :
 

Code Commentaires
class exemple {
  public static void main(String Arg[]){  
    capacite p1=new capacite();
    capacite p2=new capacite();
    p1.longueur = 16;
    p1.largeur=3;
    p1.hauteur=2;

     //
    p2.longueur = 25;
    p2.largeur=6;
    p2.hauteur=3;

    //
    p1.CalcVolume();

    //
    p2.CalcVolume();

  }

}  
            
Deux variables de type capacite sont déclarées; p1 et p2. L'opérateur "new" permet de créer deux objets de type "capacite", qui seont référencés par les variables p1 et p2.

Des valeurs sont ensuite données à ces variables.

L'appel de la méthode CalcVolume() de l'objet p1 conduit à l'affichage sur l'écran de:

Volume: 80

Le m^eme appel sur l'objet p2 conduit à:

"Volume: 450" sur l'écran.

Lorsque p1.ValcVolume() est appelée, l'interpréteur Java transfère le contr^ole au code de la méthode "CalcVolume", définie dans la classe "capacite". Quand toutes les instructions ont été exécutées, l'exécution reprend dans le programme principal après l'appel de la méthode.

Méthode avec paramètres, valeur retournée.

La majorité des méthodes prennent des paramètres. Souvent aussi, elles renvoient une valeur.

Ainsi notre exemple pourrait ^etre réécrit différemment:
 

Code Commentaires
class capacite {
  double CalcVolume (double long, 
                     double larg,
                     double haut) {
    double volume=long*larg*haut ;
    return volume;
  }
} 
La méthode "CalcVolume" a été modifiée pour recevoir trois paramètres, et retourner un nombre de type "double" comme l'indique l'instruction "return".
class exemple {
  public static void main(String Arg[]){  
    capacite p1=new capacity();
    capacite p2=new capacity();

    double vol;

    vol=p1.CalcVolume(10,3,2);

    System.out.println("Volume: " + vol);  

    //

    vol=p2.CalcVolume(25,5,2);

    System.out.println("Volume: " + vol);  
  }
} 
            
L'appel à la méthode comporte maintenant les paramètres désirés. La valeur returnée est stockée dans la varialble "vol", qui doit ^etre d'un type compatible avec celui de la méthode.

Les constructeurs sont un aspect important des classes. Ces fonctions spéciales définissent ce qui arrive lorsqu'un objet d'une classe est créée. La majorité des classes définissent explicitement les actions à réaliser dans la définition de la classe. Sinon, Java fournit lui-m^eme un constructeur par défaut (comme dans notre exemple).

Le constructeur est une méthode qui porte le m^eme nom que la classe. Il est automatiquement exécuté lorsque l'opérateur "new" est rencontré.

Les constructeurs renvoient implicitement le type de la classe créée. Le constructeur effectue généralement l'initialisation de l'objet (mise à zéro des membres de la classe...). Ainsi, le code qui instancie un objet peut l'utiliser immédiatement, son état est bien déterminé. Les constructeurs peuvent prendre des paramètres (omme les autres méthodes). Modifions notre exemple afin d'observer tous ces points:
 

Code Commentaires
Class capacite {
  double long;
  double larg;
  double haut;

  //
  capacite(double l, 
           double a, 
           double p){  
    long=l;
    larg=a;
    haut=p;
  }

  //
  void CalcVolume () {
    double volume ;
    volume=long*larg*haut;
    return volume;
  }
} 
            
On ajoute un constructeur à la classe, qui ressemble à une méthode normale, sauf qu'il n'a pas de type, et qu'il porte le m^eme nom que la classe. Le constructeur initialise les variables de l'objet avec les paramètres qu'il reçoit.
class exemple {
  public static void main(String Arg[]) {  
    capacite p1=new capacite(10,5,2);
    capacite p2=new capacite(25,6,2);
    double vol;
    vol=p1.CalcVolume();
    System.out.println("Volume: " + vol);  

    //
    vol=p2.CalcVolume();
    System.out.println("Volume: " + vol);  
  }
} 
L'opérateur "new" crée deux instances de la classe, en passant les paramètres désirés. 

Lorsqu'il n'existe plus aucune référence à un objet, Java suppose que l'objet n'est plus utilisé, et il est automatiquement détruit, c'est à dire que la mémoire qui lui était allouée est libérée. Il n'est donc pas nécessaire de détruire explicitement les objets créés.

Néanmoins, la méthode "finalize()" est fournie pour ajouter une terminaison aux classes. Cette méthode est appelée juste avant la destruction de l'objet; elle comportera donc les actions à exécuter avant que l'objet ne disparaisse.

Surchargement de méthode.

Le polymorphisme est une des caractéristiques des langages orientés objet. Java l'implémente par le moyen du surchargement (overloading) de mtéhode.

Le principe est que plusieurs méthodes avec un nom commun puissent ^etre définies dans une m^eme classe; elles se distinguent par leur liste d'arguments.

Chaque variante de la méthode surchargée peut effectuer des actions différentes, mais la compréhension du programme devient alors délicate ; il est souhaitable que toutes les variantes réalisent des actions similaires, mais avec des paramètres différents. Les constructeurs étant d'abord des méthodes, ils peuvent aussi ^etre surchargés. Par exemple, on peut construire un carré à partir de son coin supérieur gauche et de son c^oté, ou à partir de deux points définissant les coins supérieur droit et inférieur gauche.

Passage des arguments.

Les langages de programmation prévoient souvent deux manières de passer les arguments à une fonction ou une sous-routine:

Ces deux notions se retrouvent dans Java : lorsque l'argument est d'un type simple il est passé par valeur, mais s'il s'agit d'un objet, le passage se fait alors par réfrence.

Contr^ole d'accès.

Un autre pilier de la programmation orientée objet est l'encapsulation. Il s'agit de lier fortement les données et les traitements qui leur sont associés. L'encapsulation apporte aussi un moyen de contr^oler l'accès aux membres d'une classe.
 

Les mots clés définissant  les propriétés d'accès sont 'public', 'private' et 'protected'.

Lorsqu'un membre ou une méthode de classe est définie comme 'public', il est accessible de toute partie du programme. Au contraire, un membre ou une méthode 'private' n'est accessible qu'à l'intérieur de sa propre classe. Par défaut, tous les membres ou méthodes sont 'public'.

Le mot-clé 'protected' est utilisé avec le concept d'héritage, expliqué au paragraphe suivant.
 

HERITAGE.

Les trois éléments caractéristiques de la programmation orientée objet sont le polymorphisme, l'encapsulation et finalement l'héritage. Le concept est qu'une classe générale, appelée super-classe (superclass) disposant d'un petit nombre de caractéristiques peut servir de base pour définir des classes plus spécialisées qui héritent des caractéristiques générales de la super-classe et apportent leurs caractéristiques propres.

La définition d'une classe B héritant des propriétés de la classe A est réalisée comme suit:
 

Class B extends A {
    // class definition
}

La classe B, qui hérite des membres et méthodes de la classe A, est une classe autonome, utilisable indépendamment de A. B peut ^etre utilisée comme classe de base pour définir une nouvelle classe, et ainsi de suite. On obtient ainsi une véritable hiérarchie.

Java interdit l'héritage multiple (contrairement à C++), qui consiste à définir une classe à partir de plusieurs super-classes.

Les membres déclarés "private" dans la super-classe ne peuvent ^etre utilisées par les classes héritées. Une classe peut accéder aux éléments de sa super-classe gr^ace au mot-clé "super". Il est ainsi possible d'accéder m^eme aux éléments de la super-classe cachés par la sous classe.

Lorsqu'une méthode d'une classe a le m^eme nom, le m^eme type et la m^eme liste d'arguments qu'une méthode de la super-classe, celle-ci est dite réécrite (overridden). Cette propriété est la base d'un des aspects les plus puissants de Java, la sélection dynamique de méthode; cela signifie simplement que le choix de la méthode à exécuter est réalisé à l'exécution du programme.

Le prochain article démontrera la puissance du concept d'héritage; nous verrons aussi des notions telles que les classes abstraites, les interfaces, etc.
 

Références


Article original en espagnol, traduit en anglais par Javier Cano, puis en français par Jean-Denis Girard

© Jose M. Fernandez 1998
LinuxFocus 1998
Contacter le Webmestre.