eTeks : Du C/C++ à JavaContactDémarrer en JavaLes classes et leur utilisation

Du C/C++ à JavaTM

Hiérarchie des classesTable des matières


Les notions de base

Objets, classes et héritage
Références
Les mots-clés de Java
Types de base
Structure d'un programme
Les packages

Objets, classes et héritage

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 :

Objets relevés de compte
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 suivant ont hérité. Les cases Numéro, Solde, Opération, Taux d'Intérêt sont des variables 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 variables 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 :

Graphe d'héritage
Figure 3. Graphe d'héritage

CompteDepot 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 variables sans méthodes ; elle peut aussi n'avoir que des méthodes sans déclarer de variable.

!

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, toutes les variables (champs) et méthodes héritées sont accessibles directement comme si elles avaient été déclarées 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).

Références

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 en Java sont soit d'un type de base (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 de base (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 de base 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.

C

La notion de pointeur du C est remplacée par la notion de référence en Java, différente de celle du C++. Les variables qui sont des références ne peuvent désigner que des objets alloués dynamiquement.

C

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 n'est pas un pointeur
             *ptrPoint2;  // ptrPoint2 est un pointeur sur Point
int    entier,            // entier est d'un type de base
      *ptrEntier;         // ptrEntier est un pointeur sur int

Comme en Java il n'est possible de déclarer que des références comparables à ptrPoint2 ou des variables d'un type de base 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.

C

Les opérateurs * et & n'existent pas en Java. Le seul opérateur d'accès aux variables et aux méthodes d'un objet est le point (.), et les opérateurs -> et :: du C++ sont absents. Tout ceci simplifie grandement la manipulation des adresses.


La création d'objet et leur manipulation sont décrites au chapitre traitant de la création des classes.

Les mots-clés de Java

abstract

default

if

private

throw

boolean

do

implements

protected

throws

break

double

import

public

transient

byte

else

instanceof

return

try

case

extends

int

short

void

catch

final

interface

static

volatile

char

finally

long

super

while

class

float

native

switch

const

for

new

synchronized

continue

goto

package

this


Les liens définis dans le tableau indiquent les endroits où sont utilisés le mot-clé pour la première fois.

C

goto et const sont des mots-clés qui sont réservés mais non utilisés par Java ; ils permettent notamment au compilateur de vérifier qu'en cas de portage d'un programme écrit en C vers Java, des mots-clés du C n'ont pas été oubliés...

C

null est une valeur utilisée pour les références inconnues et qui ne désignent aucun objet. Il ne s'écrit pas en majuscules comme en C.

Les mots-clés du C/C++ absent en Java

auto

extern

register

typedef

#define

friend

sizeof

union

delete

inline

struct

unsigned

enum

operator

template

virtual


Les liens définis dans le tableau désignent les endroits où il est traité de la disparition de ces mots-clés.

Types de base

Applet ScrollText

TYPE

DESCRIPTION

VALEUR PAR DEFAUT

byte

Entier signé occupant 8 bits (valeurs de -128 à 127)

0

short

Entier signé occupant 16 bits (valeurs de -32768 à 32767)

0

int

Entier signé occupant 32 bits (valeurs de -2147483648 à 21474836487).

C

Le type int occupe toujours la même taille en Java : 32 bits.

0

long

Entier signé occupant 64 bits (valeurs de -9223372036854775808 à 9223372036854775807)

C

Le type long occupe 64 bits en Java contrairement à 32 bits en C.

0L

float

Nombre à virgule flottante occupant 32 bits (IEEE 754)

0.0f

double

Nombre à virgule flottante occupant 64 bits (IEEE 754)

0.0d

char

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 :

Applet Unicode

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).

C

Le type char occupe 16 bits en Java contrairement à 8 bits en C. Utilisez byte pour un 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'

boolean

Booléen dont la valeur est true ou false

C

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 :

C

Le modifieur unsigned du C n'existe pas en Java, où tous les types entiers sont signés. Comme en C, les entiers peuvent prendre pour valeur des littéraux notés sous forme décimale (i=10) , hexadécimale (i=0x0A) ou octale (i=012).


Chacun des types de base 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 de bases 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.

Structure d'un programme

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 nomDuPackage;
 
/* 2. Zéro ou plusieurs import */
import nomDeClasse;              // Importer une classe sans package
import nomDuPackage.nomDeClasse; // Importer une classe d'un package
import nomDuPackage.*;           // 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,
{                                 // et par convention 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.

C

Les commentaires s'écrivent de la même manière en Java qu'en C++ :

  • Tout ce qui suit // est ignoré jusqu'à la fin de la ligne.
  • Tout ce qui est compris entre /* et */ est ignoré. Ces commentaires peuvent incorporer des commentaires écrits avec la syntaxe précédente //, mais pas d'autres commentaires avec la syntaxe /* */.
    Il est conseillé d'utiliser d'abord les commentaires avec // pour permettre d'imbriquer ce type de commentaire, dans ceux utilisant /* */ au cas où vous ayez de besoin de commenter tout un bloc. Par exemple :
    class Classe1
    {
      /* Bloc inutilisé
      int x = 1; // x sert à ...
      */
     
      // ... 
    }
  • Il existe un troisième type de commentaire utilisant la syntaxe précédente : Les commentaires javadoc. javadoc est une commande qui utilise tous les commentaires compris entre /** et */ et respectant une syntaxe spéciale pour générer automatiquement une documentation des classes. Toute la documentation des classes fournie par Javasoft est créée grâce à cet outil.

C

L'équivalent de #include est 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.

C

Java est un langage "pur" objet et ne permet de définir au niveau global qu'UNIQUEMENT des classes ou des interfaces : Pas de constantes, ni de macros (#define du C), pas de variables globales qu'elles soient statiques ou non, pas de types autres que des classes (typedef est inutile), et toutes les fonctions ne peuvent être déclarées que comme méthodes appartenant à une classe.
C'est la raison pour laquelle vous verrez que tous les exemples donnés déclarent des classes qui sont souvent inutiles pour expliciter tel ou tel problème, mais obligatoires pour que l'exemple puisse être compilé.

Les packages

import

Aussitôt que vous utilisez une classe fournie avec le Java Development Kit ou par d'autres sources, vous devez indiquer à votre programme où la trouver. Pour cela, il faut utiliser la clause import pour chacune (ou chaque groupe) des classes dont vous voulez vous servir dans un fichier .java. Ces clauses se placent en début de fichier avant la déclaration de la première classe ou interface du fichier :

import nomDeClasse;              // importer une classe sans package
import nomDuPackage.nomDeClasse; // importer une classe d'un package
import nomDuPackage.*;           // importer toutes les classes d'un package

import est suivi soit directement d'un nom de classe, soit d'un nom de package, suivi lui-même d'un nom de 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 connait pas encore, il cherchera notamment dans les packages suivis d'un astérisque si Classe1 existe.
La classe nomDeClasse peut correspondre soit à un fichier source nomDeClasse.java, soit à un fichier compilé nomDeClasse.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 deux cas :

!

Si un package nomDuPackage comporte des sous-packages (par exemple nomDuPackage.sousPackage), la clause import nomDuPackage.* n'importe pas ces sous packages. Il faut explicitement importer chacun d'eux (avec par exemple import nomDuPackage.sousPackage.*).

Les packages peuvent servir aussi à donner un nom complet d'accès à une classe quand il y a risque de conflit pour retrouver une classe : Par exemple, à chaque fois que vous avez besoin de la classe String, vous pouvez utiliser String seul, mais, si dans un de vos fichiers .java vous définissez une autre classe String (ce qui n'est pas très conseillé !), vous pourrez accéder dans le même fichier à votre nouvelle classe String et à la classe String du package java.lang en écrivant pour cette dernière java.lang.String, à chaque fois qu'il y a risque de confusion.
Cette caractéristique est particulièrement appréciable le jour vous serez amené à utiliser des packages de sources différentes qui ont défini certaines classes avec le même nom.

C

import permet d'importer les classes définies dans d'autres répertoires, mais n'est pas obligatoire pour importer entre elles les classes définies dans un même répertoire, et notamment le répertoire courant de développement.

C

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 rien ne vous empêche de créer votre propre bibliothèque, pour y rassembler par exemple un ensemble 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 nomDuPackage;

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 contrôles d'accès à des classes représente aussi un exemple simple d'utilisation des packages.


eTeks : Du C/C++ à JavaContactDémarrer en JavaLes classes et leur utilisationDébut de la page

eTeks
© 1997-2000 Emmanuel PUYBARET / eTeks - Tous droits réservés

Hiérarchie des classesTable des matières