next up previous


Université Pierre et Marie Curie
Année 1997-1998
Maitrise d'Informatique
Module MA1 : Programmation
Séance 4

Duke.ps

Objectifs :Initiation au langage Java, le noyau objet : classe, instance, héritage, liaison, surcharge, classe abstraite.

Présentation rapide de Java

Le langage Java est un langage orienté objet à la syntaxe proche du langage C. Son apprentissage est rapide pour un programmeur connaissant un langage objet et le langage C. Dans cette présentation, on ne s'intéressera uniquement au noyau du langage en utilisant des entrées/sorties sommaires. Tout l'interfaçage avec les bibliothèques, en particulier les bibliothèques graphiques, sera étudié au TD suivant.

Classes et Objets

On appelle classe la description du regroupement des données et des procédures (méthodes) qui les manipulent. On définit la classe Point qui contient deux champs de données, deux constructeurs, et trois méthodes : deux procédures de déplacement absolu et relatif d'un point et une fonction de calcul de distance par rapport à l'origine.

class Point {
  /**
   * la classique classe Point
   */
  int x, y;

  Point(int a, int b){x=a;y=b;}
  Point(){x=0;y=0;}

  void moveto (int a, int b){x=a;y=b;}
  void rmoveto (int dx, int dy){x+=dx;y+=dy;}
  void affiche(){System.out.println("(" + x + "," + y + ")");}
  double  distance(){return Math.sqrt(x*x+y*y);}
}

Un objet est une valeur d'une classe, appelée instance de cette classe. On crée une instance par l'appel à new suivi du nom d'un constructeur et de ses paramètres.

class Test {
  /**
   * test de Point 
   */
  public static void main(String args[])
  {
    Point p0 = new Point();
    Point p1 = new Point(3,4);

    p0.affiche();    p1.affiche();
    p0.rmoveto(7,12);
    p1.rmoveto(5,6);
    p0.affiche();    p1.affiche();
    if (p0.distance() == p1.distance()) 
      System.out.println("c'est le hasard");
    else 
      System.out.println("on pouvait parier");
   }
}

Une classe contenant une méthode main est considérée comme une application qui pourra être exécutée. La méthode main sera exécutée au chargement de la classe Test, par exemple en écrivant : java Test correspondant au chargement du fichier Test.class. L'option -verbose, en mode verbeux de l'interpréteur d'instructions de la machine Java permet d'afficher les différentes classes chargées. On reviendra sur les déclarations (public et static.

Héritage

C'est l'avantage majeur de la programmation objet que de pouvoir étendre le comportement d'une classe existante tout en continuant à utiliser le code écrit par la classe originale. Quand on étend une classe, la nouvelle classe hérite de tous les champs, de données et de méthodes, de la classe qu'elle étend.

Voici une extension de la classe Point qui hérite des coordonnées et des déplacements de point et du calcul de distance :

class PointColore extends Point {

  String c;

  PointColore(){c="blanc";}
  PointColore(int a, int b, String x){moveto(a,b);c=x;}
  void affiche(){System.out.println("(" + x + "," + y + ") de couleur " + c);} 
}

Cette classe redéfinit la méthode affiche pour tenir compte du champ couleur. On dit que la méthode affiche redéfinit celle de son ancêtre. Elle doit avoir le même type de retour.

Le programme de test est proche du précédent : montrera bien

class TestC {
  /**
   * test de PointColore
   */
  public static void main(String args[])
  {
    PointColore p0 = new PointColore();
    Point p1 = new PointColore(3,4,"bleu");

    p0.affiche();    p1.affiche();
    p0.rmoveto(7,12);
    p1.rmoveto(5,6);
    p0.affiche();    p1.affiche();
    if (p0.distance() == p1.distance()) 
      System.out.println("c'est le hasard");
    else 
      System.out.println("on pouvait parier");
   }
}

Un constructeur d'une classe descendante effectue un appel explicite au constructeur ancêtre (super ()). L'exercice 0 demande de tracer les appels aux constructeurs des classes Point et PointColore.

A noter que p0 est déclaré du type PointColore et que p1 est du type Point tout court. On effectue ici une coercion IMPLICITE de types. Elle est autorisée dans la mesure où l'on affecte une variable de type "classe ancêtre" par une instance d'uns classe descendante. Tous les champs existants dans la classe ancêtre existent dans la classe descendante. Ici comme les méthodes ne sont pas déclarées statiques, les deux points auront le comportement d'instances de PointColore. L'exercice 1 étend cet exemple.

Liaisons

Quand un programme s'exécute, il doit établir la valeur associée à chaque identificateur rencontré. Cette liaison entre un identificateur et sa valeur peut s'effectuer soit à la compilation (liaison statique ou précoce), soit à l'exécution (liaison dynamique ou retardée). Ce problème se posait avant la programmation par objet (par exemple la majorité des dialectes Lisp interprétés possèdent une liaison dynamique, et les dialectes ML compilés une liaison statique).

En programmation objet les liaisons concernent aussi les méthodes. Les langages à objets utilisent la liaison retardée pour implanter le polymorphisme ad hoc où le même envoi de message peut déclencher différents codes à s'exécuter selon l'objet receveur. C'est l'objet lui-même qui saura le code à exécuter.

Java est par défaut en liaison dynamique mais il est possible d'indiquer explicitement une liaison statique.

Surcharge, this, self et accès aux champs,

On s'intéresse principalement à la description statique des champs (à comparer au défaut dynamique). Les accès peuvent aussi être protégés par des déclarations public, private, protected ou du paquetage ( anonyme si aucun paquetage n'est indiqué).

Un champ static est un champ unique pour la classe (et donc ses instances). Pour les données, le champs sera partagé par toutes les instances. Une utilisation peut être un compteur commun à toutes les instances. Une méthode statique est une méthode connue au niveau de la classe. Si on envoie le message correspondant à une instance de cette classe, ce sera cette méthode qui sera exécutée. Cette résolution s'effectue à la compilation. Une sous-classe peut surcharger une méthode statique par une nouvelle définition statique de celle-ci. En Java, les méthodes statiques ne peuvent modifier un champs de données non statique. De plus elles en peuvent être surchargées par une méthode non statique. Ces limitations les rendent peu utilisées.

Sans précision les champs sont déclarés dynamiques . Pour les champs de données cela signifie que chaque instance aura ses propres champs. Une affectation de ceux-ci n'aura d'effets que pour cette instance. Pour les méthodes cela signifie que la résolution de la méthode à employer s'effectue à l'exécution (c'est l'équivalent de virtual de Pascal Object). L'instance contient la liste des méthodes dynamiques. L'envoi d'un message va vérifier quelle méthode employée dans cette liste.

L'exercice 2 contient un exemple des différents comportements selon les déclarations statique ou dynamique. En cas d'ambiguïté, le symbole this indique soi même et le symbole super son ancêtre direct.

Les champs peuvent être public (visibles de tout le monde), private connu que de la classe et protected accessible uniquement par la sous-hiérarchie de la classe. Les champs de données sont en général private. Si rien n'est précisé, les champs sont connus au niveau du paquetage (voir prochain TD). Si aucun paquetage n'est défini, alors le champ est visible du paquetage anonyme (ce qui est proche de public).

Exceptions

Une exception est un objet, instance avec un type, des méthodes et des données. Les objets exceptions dérivent de la classe Throwable. On ne s'intéresse qu'à celles dérivées de sa sous-classe Exception.

L'exemple suivant montre une méthode risquant d'effectuer une division par zéro si le dénominateur vaut 0.

class Exemple { 
  
  public int division(int a,int b)

  {
      return (a/b);
  }
}

On le réécrit en utilisant des exceptions :

class Division_par_zero extends Exception {
}


class Exemple { 
  
  private int division_aux(int a,int b)
  throws Division_par_zero
  {   if (b == 0) throw new Division_par_zero();
      else return (a/b);
  }

  public int division(int a, int b)
  {
    try { return division_aux(a,b); }
    catch(Division_par_zero e) { return 0;}
    finally {System.out.println("on passe ici");}
  }

}

La clause finally est facultative. Son code est exécuté à la sortie du try.

Il y a deux types d'exceptions : les exception vérifiées (déclarées en début de méthode par la clause throws) et les exceptions non vérifiées (extensions de RuntimeException et de Error). En règle générale on préférera les exceptions vérifiées pour des questions de sûreté de récupération. L'exemple de l'exercice 2 ne récupère pas les exceptions. Selon le compilateur Java un message d'attention ou une erreur s'affiche.

GC

On alloue explicitement de la mémoire par new mais la récupération est implicite. Il n'y a pas de free ou dispose. Normalement la récupération d'un objet n'est pas visible. Néanmoins une classe peut implanter la méthode finalize qui sera exécutée juste avant qu'un objet ne soit réclamé. Cela permet de libérer des ressources non gérées par Java comme un fichier sur le disque. Une question de l'exercice 3 propose une trace du nombre de doublets vivants pour les listes.

Classes abstraites

Les classes abstraites sont des classes dont certaines méthodes sont déclarées mais ne possèdent pas de corps. Ces méthodes sont dites alors abstraites. Il n'est pas possible d'instancier une classe abstraite (new est interdit). On utilise le mot clé abstract pour le préciser. Si une sous-classe, d'une classe abstraite, redéfinit toutes les méthodes abstraite de l'ancêtre, alors elle devient concrète, sinon elle reste abstraite.

Compilation et exécution d'un programme

Sur les machines Linux de l'UFR (ippcXY), on trouve le compilateur Java de chez SUN. On utilisera les commandes suivantes :

Le compilateur javac prend en entrée un fichier d'extension .java et produit un ou plusieurs fichiers d'extension .class. Chaque fichier .class correspond au code d'une classe. On exécute une classe (contenant une méthode main) en lançant l'interprèteur de la machine virtuelle sur la classe (java nom_de_la_classe).

Exercice 0 : trace des constructeurs

On reprend les définitions des classes Point et PointColore en modifiant de la manière suivante les déclarations des constructeurs :

class Point {

...

  Point(int a, int b){System.out.println("Point("+a+","+b+") D");x=a;y=b;}
  Point(){System.out.println("Point() D");x=0;y=0;}

 ...
}

class PointColore extends Point {

...

  PointColore(){
    System.out.println("PointColore() D");
    c="blanc";
    System.out.println("------------ F");}

  PointColore(int a, int b, String x){
    System.out.println("PointColore("+a+","+b+","+x+") D");
    moveto(a,b);
    c=x;
    System.out.println("************ F");}

...
}

Qu'affichent les exécutions des classes Point et PointColore?

Solution

you: java Test

Point() D
Point(3,4) D
(0,0)
(3,4)
(7,12)
(8,10)
on pouvait parier
Les constructeurs de Point tracent leur appel (+... D+).
you: java TestC

Point() D
PointColore() D
------------ F
Point() D
PointColore(3,4,bleu) D
************ F
(0,0) de couleur blanc
(3,4) de couleur bleu
(7,12) de couleur blanc
(8,10) de couleur bleu
on pouvait parier
L'appel d'un constructeur de PointColore déclenche tout d'abord l'appel au constructeur ancêtre sans paramètres (ici Point()).

Exercice 1 : pour faire le point

1.
Ecrire une classe point décrivant les coordonnées cartésiennes d'un point, sachant répondre à un message de déplacement et sachant s'afficher. On utilisera System.out.println pour afficher les coordonnées du point.
2.
Etendre la classe point par la classe pointc contenant un champ couleur de type color. La méthode d'affichage devra tenir compte de la couleur.

3.
Etendre la classe point par la classe point3D contenant un champs pour la 3ème dimension.

4.
Réfléchir pour la semaine prochaine à comment réaliser une classe de points colorés en 3D?

Exercice 2 : mot de passe

On cherche à définir une classe Chaîne et une sous-classe Mot de passe. Cette sous-classe n'affichera pas les caractères tapés et les remplacera par des étoiles.
Soit la classe Istring suivante :

class Istring{
  
  private String txt;

  public Istring(){txt="";}
  public String value(){return txt;}
  public void input(String msg) {
    System.out.print(msg + " : ");
    System.out.flush();
    txt=read_str();
    System.out.println();
  }

  public String read_str() {
    String s = new String();
    char c;
    while ((c=(char)System.in.read()) != '\n')
      {System.out.print(c);System.out.flush();s=s+c;}
    return s;
 }
}
et la sous-classe PIstring :
class PIstring extends Istring {

  public PIstring(){super();}

  public String read_str(){
    String s = new String();
    char c;
    do 
    {
      c = (char)System.in.read();
      if (c != '\n') {System.out.print('*');System.out.flush();s=s+c;}
    }
    while (c != '\n');
    return s;
  }
}
et enfin la classe Test2 :

1.
Qu'affiche le programme suivant? On supposera que le terminal est en mode caractère sans echo (voir la commande d'Unix stty).
class Test2 { 


  public static void main(String args[])
    { Istring s1 = new Istring();
      PIstring s2 = new PIstring();

      s1.input("login");
      s2.input("passwd");
      System.out.println("\n\n");
      System.out.println(s1.value() + " a " + s2.value() + " comme mot de passe");
    }
}
Solution

La commande run lance un shell sh et met le terminal en mode sans écho (stty -noecho et en mode caractère (stty cbreak) puis exécute java Test2.

ippc67{chaillou}268: run
login : emmanuel
passwd : **********



emmanuel a tartempion comme mot de passe

2.
Y a t-il des variations d'affichage selon les déclarations static ou non des méthodes suivantes?
Affichage Istring.read_str PIstring.read_str
static
static
static static

Solution

login passwd Istring.read_str PIstring.read_str
toto ****
erreur compil static
erreur compil static
toto truc static static

Dans le cas où tout est dynamique, le input de la chaîne affiche bien des étoiles à la place des caractères. Si les deux sont statiques alors le mot de passe s'affiche car la méthode input est liée à la méthode statique de la classe ancetre. Dans les deux autres cas il y a une erreur à la compilation due aux restrictions de Java.

Exercice 3 : listes d'entiers en Java

1.
Ecrire une classe abstraite Liste décrivant les méthodes display, null, head, tail et len...
2.
Ecrire les deux sous-classes Nil et Cons. La classe cons aura deux champs de données car (entier) et cdr (liste). On décrira différents constructeurs pour la classe cons prenant soit un entier (que l'on ajoute en tete
3.
Ecrire une classe Test implantant la recherche des nombres premiers selon la méthode du crible d'Erathostène.

Solution


Fichier : list.java
abstract class List { // classe abstraite racine de l'arbre d'heritage
    abstract  boolean empty() ;
    abstract int head() throws EmptyList;
    abstract List tail() throws EmptyList;
    abstract List cons(int x);
    abstract void display();
}

class EmptyList extends Exception {}


class Nil extends List {
   static List NIL = null;
   boolean empty(){return true;}
   List cons(int x){return new Cons(x,this);}
   int head()throws EmptyList {throw new EmptyList();}
   List tail()throws EmptyList{throw new EmptyList();}
   void display(){System.out.print("[]");}
}


class Cons extends List {
  int car;
  List cdr;
  Cons(){car=0;cdr=Nil.NIL;}
  Cons(int a, List b){car=a;cdr=b;}
  
  boolean empty(){return false;}
  List cons(int x) {return new Cons(x,this);}
  int head(){return car;}
  List tail(){return cdr;}
  void display(){System.out.print(car + "::"); cdr.display();}
}



class ListPrem extends Cons {
  
                  
  ListPrem(int n){List l; 
                  if (n<=2) {car=2;cdr=new Nil();}
                  else{l = crible(n);car=l.head();cdr=l.tail();}
  }

  private List intervalle(int a, int b) {
    if (a > b) return new Nil(); 
    else return new Cons(a,intervalle(a+1,b));
  }

  private List mult_filter(int a, List l)
  { if (l.empty()) return l;
    else if (((l.head()) % a) == 0) return mult_filter(a,l.tail());
         else return new Cons(l.head(),mult_filter(a,l.tail()));
  }
	
  private List crible_aux(List l)
  { if (l.empty()) return l;
    else return new Cons(l.head(), crible_aux(mult_filter(l.head(),l.tail())));
  }

  private List crible(int n)
  { return crible_aux(intervalle(2,n));}
 
}

class Test {
  public static void main(String args[])
  {  ListPrem l= new ListPrem(1000); 
     l.display();
     System.out.println();
  }
}

Exercice 4 : Arbres d'expressions

On cherche à étendre les fonctionnalités de la classe ExpAr vue en cours. Soit le début de la hiérarchie suivante :

abstract class ExpAr { // classe abstraite racine de l'arbre d'heritage
    abstract int eval () throws Div0;
    abstract String scp () ;
}

class Cte extends ExpAr { // premiere sous-classe concrete
    int val ;
                  Cte (int v) {val = v ;}
    int eval () { return val ;}
    String scp () {return (new Integer(val)).toString() ;}
}

abstract class Bin extends ExpAr {
    ExpAr og, od ;
    abstract int oper (int g , int d) throws Div0;
    abstract String cop () ;

    int eval () throws Div0 {return oper (og.eval(), od.eval()) ;}
    String scp () {return ("("+ og.scp() + cop() + od.scp() +")") ; }
    void init (ExpAr g, ExpAr d) {og = g ; od = d ;}
}

class Add extends Bin {
    Add (ExpAr g, ExpAr d) {init (g, d) ;}
    int oper (int g, int d) {return (g+d) ;}
    String cop () {return ("+") ;}
}

class Mult extends Bin {
    Mult (ExpAr g, ExpAr d) {init (g, d) ;}
    int oper (int g, int d) {return (g*d) ;}
    String cop () {return ("*") ;}
}

1.
Ajouter une classe Div. La méthode eval déclenche une exception si le dénominateur vaut 0.
2.
Définir une classe abstraite ExpBool pour les expressions booléennes.
3.
Définir les sous-classes pour les constantes booléennes, les opérateurs unaires et les opérateurs binaires. On implantera les opérateurs Not, Or et Equal sur les entiers.
4.
Ajouter une classe Cond à la hiérarchie ExpAr pour traiter l'expression conditionnelle : x = 4 ? 7 : 19 qui retourne 7 si x vaut 4 et 19 sinon.

Solution tout n'est pas fait...


Fichier : b.java
class Div0 extends Exception {}

class Div extends Bin { 
  Div (ExpAr g, ExpAr d) {init(g,d);}
  int oper (int g, int d) throws Div0
    {if (d != 0) return g/d; else throw new Div0();}
  String cop() {return("/");}
}

abstract class ExpBool { // classe abstraite racine de l'arbre d'heritage
    abstract boolean eval () ;
    abstract String scp () ;
}
 
class CteBool extends ExpBool { // premiere sous-classe concrete
    boolean val ;
    CteBool (boolean v) {val = v ;}
    boolean eval () { return val ;}
    String scp () {return (new Boolean(val)).toString() ;}
}

abstract class Pred extends ExpBool {
    ExpBool og, od ;
    abstract boolean oper (boolean g , boolean d) ;
    abstract String cop () ;

    boolean eval () {return oper (og.eval(), od.eval()) ;}
    String scp () {return ("("+ og.scp() + cop() + od.scp() +")") ; }
    void init (ExpBool g, ExpBool d) {og = g ; od = d ;}
}

class Equal extends Pred {
  Equal(ExpBool g, ExpBool d){init(g,d);}
  boolean oper (boolean g, boolean d){return (g==d);}
  String cop(){return ("==");}
}

Exercice facultatif : Piles hétérogènes en Java

A la manière des listes de l'exercice 3, on construit une hiérarchie Pile dans le but d'écrire un petit évaluateur d'un langage à pile à la PostScript. Les objets de la pile seront soit de la classe Object, soit d'une classe Element sous-classe d'Object à définira. Les méthodes sont bool empty(), void push (Element e) et Element pop().

1.
Ecrire cette hiérarchie.
2.
Etendre les piles avec les opérateurs : void dup() qui duplique le sommet de pile, ..., void print() qui affiche le sommet de pile.
3.
Définir une classe ContexteGraphique pour décrire le point courant et la couleur courante.
4.
Ecrire une classe Environnement contenant un champ Pile, un champ Contexte_graphique.
5.
Définir une classe abstraite Instruction possédant une méthode eval(Environnement env). Implanter l'instruction add qui additionne les deux valeurs en haut de la pile et empile le résultat, et l'instruction moveto qui déplace absolument la valeur du point courant par rapport aux deux coordonnées qui se trouvent sur le haut de la pile.
6.
Définir une classe Programme qui lit sur l'entrée standard des expressions du langage, empile les valeurs et exécute les les instructions.

Utilisation des programmes décrits

On trouvera sur la machine maya.cicrp.jussieu.fr la plupart des programmes décrits dans ce TD dans le catalogue users/p6ipens/chaillou/enseignements/97-98/programmation/TD4.

Bibliographie

Voici quelques livres parmi les très nombreux ouvrages sur Java :

et quelques liens locaux et distants de documentations utiles :


next up previous
Emmanuel CHAILLOUX
11/20/1997