Introduction à JAVA

Les objets en JAVA

    Les concepteurs de langages cherchent les moyens de faciliter la création et la maintenance du code. Une méthode souvent retenue consiste à structurer le langage au moyen de « procédures » qui sont des blocs de code effectuant une action bien précise sur des données du programme. Il est possible de regrouper ces procédures en bibliothèques utilisables par d'autres programmeurs. Cette façon de faire est efficace mais présente l'inconvénient de séparer le code et les données et d'être sensible aux effets de bord. L'idée de base des langages objets est de regrouper données et code en une même entité l'objet. Cette réunion données-code se nomme « encapsulation ».

Quelques définitions relatives aux classes :

Classe  : c'est le moule qui permet de fabriquer les objets.

Objet  : c'est une réalisation concrète et utilisable de la classe. En jargon objet, on dit que c'est une « instance » de la classe.

Méthodes  : ce sont des procédures qui réalisent les actions que l'on peut effectuer sur l'objet.

Message  : c'est l'appel d'une méthode de l'objet.

3.1 Classes et objets dans JAVA

Nous allons illustrer la description des classes par un exemple simple. Nous allons créer la classe « rectangle » qui affiche et manipule des rectangles.

3.1.1 Déclaration de classe

La syntaxe de la déclaration d'une classe est :

class rectangle {
. . .
}

3.1.2 Variables et méthodes d'instance

Les variables d'instance sont les données propres à chaque objet (instance de la classe). Les variables relatives à deux objets différents sont physiquement distinctes : elles occupent des cases mémoires différentes.

Pour définir un rectangle, il faut connaître la position d'un sommet (supérieur gauche par exemple) sa largeur et sa hauteur.

class rectangle {
int orx,ory,large,haut;
. . .
}

Les méthodes vont déterminer le comportement des objets rectangle .
La syntaxe de la déclaration d'une méthode est :

    Type du résultat produit par la méthode ou void si elle ne produit pas de résultat ;
    Le nom de la méthode ;
    Le type et le nom des arguments placés entre parenthèses ( ). Les parenthèses même vides sont obligatoires.
             Le mode de passage des arguments des méthodes est précisé dans une note .
    Un bloc d'instruction qui constitue la méthode. Sauf pour les méthodes de type void, le bloc de code est terminé par return le_resultat.

La première méthode calcule la surface du rectangle :

int surface()
{  int surf=large*haut;
    return surf;}

La seconde dessine le rectangle en noir. On utilise ici des instructions de java.awt qui est un ensemble de classes utilitaires livrées avec JAVA. L'instruction " g.setColor(Color.black) " signifie que la couleur du pinceau pour écrire dans l'objet graphique g est la couleur Color.black .

void dessine()
{  g.setColor(Color.black);
    g.drawRect(orx, ory, large, haut);}

L'objet graphique g doit être défini comme variable pour la classe par l'instruction «  Graphics g  ;  ».

3.1.3 Constructeurs

C'est une méthode particulière qui indique comment initialiser la classe.
Cette méthode doit porter le nom de la classe.

rectangle(Graphics appG, int x, int y, int l, int h)
{  g= appG;
    orx = x;  ory = y;  large = l; haut = h; }

Pour chaque nouvel objet rectangle, ce constructeur initialise ses variables d'instances avec les valeurs des paramètres passées au constructeur : appG, x, y, l et h.

Dans une classe, la présence des constructeurs est facultative.

Voici le code complet de notre classe rectangle .

class rectangle{
    int orx,ory,large,haut;   //variables d'instance
   Graphics g;

rectangle (Graphics appG, int x, int y,int l, int h) //constructeur
{    g=appG;
      orx=x;  ory = y; large = l; haut = h;}

int surface() //méthodes
{  int surf=large*haut;
    return surf;}

void dessine()
{  g.setColor(Clor.black);
    g.drawRect(orx, ory, large, haut);}
}

3.1.4 Appels des méthodes

L'invocation d'une méthode se fait en donnant le nom de l'instance puis celui de la méthode en précisant les arguments (éventuels) de celle-ci.

Si la liste des arguments est vide, la présence des parenthèses est quand même nécessaire.

Exemple :
Pour dessiner un rectangle de nom rec , le message d'appel à la méthode sera  : rec.dessine( ) ;

3.1.5 Utilisation d'une classe

Pour pouvoir utiliser la classe rectangle, nous allons créer une applet dont le détail du code sera explicité ultérieurement.

import java.applet.*;
import java.awt.*;

public class testrect extends Applet
{  rectangle r1,r2;    // note 1

public init()
{  setBackground(Color.ligthGray);
    r1=new rectangle(getGraphics(),10,20,100,50);   //note 2
    r2=new rectangle(getGraphics(),10,80,100,60)}; //note 3

public void paint(Graphics g)
{  int x=r1.surface();          //note 4
    g.drawString(""+x,250,100); //note 5
    r1.dessine();                //note 6
    r2.dessine();}
}

Ce programme (applet) est une classe qui ne possède pas de constructeur et deux méthodes que l'on retrouve dans toute les applets. Au démarrage de l'applet, le navigateur appelle la méthode init( ) puis la méthode paint ( ) qui dessine dans la fenêtre octroyée par le navigateur à l'applet.

    Note 1  : on définit deux instances de la classe rectangle r1 et r2 ;
    Note 2  : on crée effectivement avec l'instruction new l'instance de nom r1 de la classe. Le compilateur réserve la mémoire nécessaire pour contenir les informations relatives à r1. L'instruction getGraphics() permet de récupérer l'objet graphique de l'applet.
    Note 3  : on crée effectivement la nouvelle instance r2 de la classe.
    Note 4  : on crée la variable x à laquelle est affectée la valeur de la surface du rectangle r1.
    L'exécution de l'instruction : r1.surface() à la place de : int x = r1.surface() provoque aussi un appel à la méthode surface mais la valeur calculée n'est pas récupérée.
    Note 5  : cette instruction entraîne l'affichage au point de coordonnées x = 250, y =100 de la valeur de x.
    Note 6  : appel de la méthode dessine( ) qui provoque le dessin du rectangle r1 dans la fenêtre de l'applet ;

Cette applet sera appelée par une page html dont le code (minimal) sera :

<html>
<head>
<title>testrect</title>
</head>
< body>
<applet code=testrect.class, width=320, height=240 ></applet>
</body>
</html>

3.1.6 Constructeurs multiples

Le concepteur de la classe rectangle se rend compte qu'en plus des rectangles, il devra manipuler aussi des rectangles pleins de diverses couleurs. Au lieu de créer une nouvelle classe, il est possible en JAVA de créer un nouveau constructeur. Il aura le même nom que le constructeur initial mais devra différer de celui-ci soit par le nombre de ses arguments , soit par leur type .
Voici une forme possible de ce nouveau constructeur (qui possède l'argument supplémentaire couleur du type Color qui est un des nombreux types de java.awt).

rectangle (Graphics appG,int x, int y, int l, int h, Color couleur)
{  g = appG;
    orx = x; ory =y;  large = l;  haut = h;
    plein = true;  col =couleur;}

Il faut ajouter deux variables d'instance à la classe rectangle : la variable col qui est de type Color et le booléen plein qui sera utilisé de façon interne à la classe afin de distinguer les rectangles simples des rectangles pleins. On pourra modifier la méthode dessine( ) pour prendre en compte les deux types de rectangles possibles.

public void dessine()
{ if (plein){
      g.setColor(col);   g.fillRect(orx,ory,large,haut);}
   g.setColor.(Color.black);
   g.drawRect(orx,ory,large,haut);}

Il est aussi possible de modifier la méthode surface pour lui faire afficher dans le rectangle la valeur de sa surface.

public int surface()
{  int sur = large*haut;
    this.dessine(); // ou dessine( )
    g.drawString(""+sur,orx+10,ory+20);
    return sur;}

Dans certains cas, il peut y avoir ambiguïté sur l'instance à prendre en compte. Le mot clé this permet de préciser que la méthode doit être appliquée à l'instance en cours d'utilisation.

3.1.7 Modificateurs et portée des variables d'instance

Les variables d'instance sont accessibles directement par toutes les méthodes de la classe. Pour respecter l'encapsulation, il ne faut pas que les données d'un objet puissent être modifiées directement depuis l'extérieur de celui-ci. Pour y parvenir, on peut limiter la portée des variables de classe (et des méthodes) au moyen de modificateurs. Dans le cas présent, les modificateurs utilisables sont private qui limite la portée des variables à la classe et public qui l'étend à toutes les classes.
Une variable de classe déclarée public est accessible depuis l'extérieur de la classe en préfixant son nom par celui de l'objet. Par défaut, variables et méthodes sont public .

3.1.8 Forme finale de l'exemple

Comme exemple de l'utilisation des modificateurs, la variable col est déclarée public  : elle est accessible à partir de la classe testrect en utilisant la syntaxe : Nom_de_l'instance.col = valeur. (noter le point entre le nom de l'objet et la variable).
Par contre, les autres variables étant déclarées private sont inaccessibles depuis testrect  : la ligne r1.orx = 50  ; placée dans paint provoque une erreur de compilation puisque « orx » a été déclarée private.

import java.applet.*;
import java.awt.*;

class rectangle1
{   private Graphics g;
     private int orx,ory,large,haut;
         private static float echelle=1.0f;
     public Color col;
     private boolean plein;

rectangle1(Graphics appG,int x, int y, int l, int h) //constructeur 1
{   g=appG;
     orx=x; ory=y; large=l; haut=h;
     plein=false;}

rectangle1(Graphics appG,int x, int y, int l, int h,Color couleur) //constructeur 2
{   g=appG;
     orx=x; ory=y; large=l; haut=h;
     plein=true; col=couleur;}

public void dessine()
{   if (plein){
        g.setColor(col); g.fillRect(orx,ory,large,haut);}
     g.setColor(Color.black);
     g.drawRect(orx,ory,large,haut);}

public void tourne()
{   int temp;
     temp=large; large=haut; haut=temp;}

public void bouge(int dx, int dy)
{   orx += dx;    ory += dy;}

public void change(int l, int h)
{   large = l;   this.haut = h;} //this est facultatif

public int surface()
{   int sur=(int)(large*haut*echelle);
     this.dessine();
     g.drawString(""+sur,orx+10,ory+20);
     return sur;}
}

//******************************************

public class testrec1 extends Applet
{   rectangle1 r1,r2;

public void init()
{   setBackground(Color.lightGray);
     r1= new rectangle1(getGraphics(),10,20,100,50);
     r2= new rectangle1(getGraphics(),10,80,100,60,Color.red);}

public void paint(Graphics g)
{   int x = r1.surface();    g.drawString(""+x,250,100);
     r1.dessine();
     r2.dessine();
     r1.tourne();
     r1.bouge(180,100);
     r1.dessine();
     r2.bouge(20,30);
     r2.col = Color.blue;
     r2.change(20,40);
     r2.dessine();}
}

Cliquer dans le cadre de l'applet pour l'activer

3.1.9 Surcharge des méthodes

De la même façon qu'il est possible de définir plusieurs constructeurs, il est possible de définir deux méthodes ayant le même nom à la condition que leur nombre d'arguments ou que les types des arguments diffèrent. Cette possibilité s'appelle la surcharge des méthodes.
Exemple : Afin de créer une homothétie, on peut surcharger la méthode :

public void change(int l, int h)
{   large = l;  haut = h;}

de la manière suivante :

public void change(float k)
{ large = (int)(large*k).
   haut = (int)(haut*k);}

3.1.10 Variables et méthodes de classe

3.1.10.1 Variables de classe (statiques)

Dans la classe rectangle, on pourrait définir la variable nombre_d_or. Cette variable aura la même valeur pour toutes les instances de la classe. Il est dont inutile de la dupliquer dans toutes les instances. Ceci est obtenu en déclarant cette variable static . Les variables statiques, déclarées static sont dites variables de classe.
Les variables de classe sont initialisées une fois pour toute lors du chargement de celle-ci.
Dans l'exemple précédent, la variable Graphics g ayant la même valeur pour toute les instances peut être déclarée static .

3.1.10.2 Portée des variables de classe

Elles sont utilisables directement dans toutes les méthodes de la classe.
Elles sont aussi utilisables depuis l'extérieur de la classe. Leur nom doit alors être préfixé par le nom de la classe (et pas par le nom d'une instance !)

3.1.10.3 Méthodes de classes

Ce sont des méthodes destinées à agir sur la classe plutôt que sur les instances. On doit les déclarer static . Pour accéder à une méthode statique depuis l'extérieur de la classe, il faut préfixer le nom de la méthode par le nom de la classe ou par le nom d'une instance de la classe.
Pour améliorer la lisibilité du code il est conseillé de préfixer le nom de la méthode par le nom de la classe .
Dans la suite, nous utiliserons uniquement cette écriture.

3.1.10.4 Exemple

L'exemple suivant donne une manière possible d'implémenter une classe permettant de manipuler les nombres complexes.

class comp
{   double a,b;                        //variables d'instance
     final static double PI = Math.PI; //variable de classe

     comp(double inita,double initb)    //constructeur
     {a=inita;       b=initb;}

     static double norme(comp x)       //méthodes de classe
     {  return Math.sqrt(x.a*x.a + x.b*x.b);}

     static double phase(comp x)
     {  return Math.atan2(x.b,x.a)*180/PI;}

     static comp somme(comp x, comp y)
     {  comp s=new comp(0,0);
        s.a = x.a + y.a;   s.b = x.b + y.b;
        return s;}

     static comp produit(comp x, comp y)
     { comp p=new comp(0,0);
       p.a = x.a*y.a - x.b*y.b;        p.b = x.b*y.a + y.b*x.a;
       return p;}

     static comp quotient(comp x, comp y)
     {  comp q=new comp(0,0);
        double n2=y.a*y.a + y.b*y.b;
        q.a = (x.a*y.a + x.b*y.b)/n2;   q.b = (x.b*y.a - y.b*x.a)/n2;
        return q;}
}

Pour utiliser cette classe, on pourra par exemple écrire :

comp a = new comp (3,5); // déclaration et initialisation
comp b = new comp (2,-4);
     comp c= new comp (0,0);
c = comp.produit (a,b); // appel d'une méthode

Remarques :
         Les méthodes sont publiques par défaut.
Math.sqrt est un appel à la méthode statique sqrt (racine carrée) de la classe « Math » de JAVA.

3.1.11 Comparaison d'objets

3.1.11.1 Références des objets

Après l'exécution du code suivant qui concerne des littéraux :

int i = 2 ;
int j = i ;
i = 4;

la variable i vaut 4 et la variable j vaut 2. Quand on travaille sur des littéraux, toute déclaration de variable réserve pour celle-ci une zone mémoire de la taille idoine (par exemple 4 octets pour un entier de type int). Le code j  =  i  recopie la valeur de la zone mémoire "i" dans la zone mémoire "j".

Il en va différemment avec des objets. Après l'exécution du code suivant :

rectangle r1, r2;
r1 = new rectangle(getGraphics(),10,20,100,50);
r2 = r1;
r1.change(60,30);

la largeur du rectangle r2 vaut aussi 60 et sa hauteur 30 car r2 et r1 correspondent en fait au même objet . Toute modification des variables de l'un modifie les variables de l'autre. On dit que r1 et r2 pointent sur un même objet.
Quand on déclare un objet, on récupère l'adresse d'une case mémoire qui est en général l'adresse de la première case mémoire de la zone réservée pour contenir toutes les données relatives à l'objet. Toute manipulation de cet objet fait référence à cette adresse (pointeur implicite).
L'instruction r2 = r1 recopie seulement l'adresse du pointeur vers r1 dans le pointeur vers r2. Pour créer une nouvelle instance indépendante d'un objet, il faut utiliser son constructeur.

3.1.11.2 Comparaison des objets

La comparaison de deux instances d'un objet (avec = = ) s'effectue sur les références de ces instances. Après exécution de :

Boolean ok = false ;
rectangle r1, r2;
r1 = new rectangle(getGraphics(),10,20,100,50);
r2 = new rectangle(getGraphics(),10,20,100,50);
if (r1 == r2) ok = true;

ok reste "false" car les références de r1 et de r2 sont différentes.
La comparaison de deux instances d'une classe nécessite l'écriture d'une méthode spécifique dans laquelle on compare la valeur de chacune des variables définies par le constructeur de l'objet..

3.2 Héritage

C'est un processus qui permet d'ajouter des fonctionnalités à une classe sans avoir à réécrire tout le code de cette classe. La nouvelle classe hérite de toutes les données et méthodes de la classe dont elle est issue. Pour signifier que la nouvelle classe dérive d'une classe mère, on ajoute à la fin de sa déclaration le modificateur extends suivi du nom de la classe mère.
Les classes filles n'héritent pas directement des constructeurs du parent : On doit, avec le mot clé super , faire appel au constructeur de la classe mère puis initialiser les variables spécifiques à la nouvelle classe. Si la première instruction du constructeur d'une sous-classe n'est pas super (référence au constructeur de la classe mère), un appel super( ) est exécuté automatiquement par le compilateur. Il faut alors qu'il existe un constructeur sans paramètre dans la classe mère.

    Par exemple pour tracer des rectangles pleins, il est possible d'utiliser une approche différente de celle qui a déjà été examinée. On peut par exemple créer une nouvelle classe rectplein qui va hériter de la classe mère rectangle . Pour que la classe rectplein puisse accéder aux données de rectangle , il faudrait que celles-ci soient publiques ce qui est contraire au principe de l'encapsulation. La solution est de définir les variables de classe avec le modificateur protected . Les membres d'une classe (données, méthodes) protégés par "protected" sont accessibles à partir des classes dérivées mais pas des autres classes.
La classe rectplein hérite de toutes les méthodes de rectangle qui ne sont pas redéfinies dans rectplein. La méthode bouge( ) est celle de rectangle  ; par contre comme la méthode dessine a été redéfinie dans la classe rectplein c'est elle qui est utilisée pour les objets de ce nouveau type.

import java.applet.*;
import java.awt.*;

class rectangle
{  protected static Graphics g;
    protected int orx,ory,large,haut;

   rectangle(Graphics appG,int x, int y, int l, int h)
    {   g=appG;
        orx=x;  ory=y;  large=l;   haut=h;}

   public void dessine()
    {  g.setColor(Color.black);
       g.drawRect(orx,ory,large,haut);}

   public void bouge (int x, int y)
    { orx +=x;   ory+=y;}
}
//******************************************
class rectplein extends rectangle
{  Color couleur;   //variable supplémentaire

   rectplein (Graphics appG,int x, int y, int l, int h, Color col)
    {  super(appG,x,y,l,h); //appel constructeur de la classe rectangle
       couleur=col;}   //initialisation de la nouvelle variable

   public void dessine()
    {  g.setColor(couleur);
       g.fillRect(orx,ory,large,haut);
       g.setColor(Color.black);
       g.drawRect(orx,ory,large,haut);}
}
//******************************************
public class testrect extends Applet
{   rectangle r1;
     rectplein rp1;

 public void init()
  {  setBackground(Color.lightGray);
     r1= new rectangle(getGraphics(),10,20,100,50);
     rp1= new rectplein(getGraphics(),100,100,100,50,Color.red);}

 public void paint(Graphics g)
  {  r1.dessine();
     rp1.dessine(); //appel méthode de "rectplein"
     rp1.bouge(30,60);   //appel méthode de "rectangle"
     rp1.dessine();}
}

Cliquer dans le cadre de l'applet pour l'activer

Arguments des méthodes .

 Beaucoup de langages autorisent le passage des arguments  soit par valeur soit par adresse . Dans le cas d'un passage d'argument par valeur, on fait une copie de la valeur des paramètres dans une pile et c'est l'adresse mémoire de cette pile qui est transmise à la méthode.  Les adresses réelles des arguments étant inconnues par la méthode, celle-ci ne peut pas les modifier.
  Dans le cas d'un passage d'argument par adresse, on transmet à la méthode l'adresse mémoire (pointeur) de la variable argument : les modifications apportées à la valeur de la variable par la méthode sont  immédiates et définitives.

Pour JAVA, le mode de passage est fonction de la nature des arguments :
Les arguments de types simples (entiers, réels, caractères ...) sont passés par valeur et les arguments d'objets ou de tableaux sont passés par adresse.
  Si l'on souhaite qu'une méthode modifie une variable de type simple, il ne faut pas passer celle-ci comme argument mais il faut la déclarer variable de classe.

Syntaxe pour les arguments de type tableau
En-tête de la méthode : type (ou void) nomDeLaMethode ( typeDuTableau nomDuTableauM [ ])  // crochets seuls.
Appel : nomDeLaMethode(nomTableauG) // pas de crochets après le nom du tableau.
Les opérations effectuées par la méthode sur les éléments du tableau "nomDuTableauM" s'appliquent en fait au tableau nomTableauG qui est le seul à avoir une adresse mémoire effective.