Université Pierre et Marie Curie
Année 1997-1998
Maitrise d'Informatique
Module MA1 : Programmation
Séance 4
Objectifs :Initiation au langage Java, le noyau objet : classe, instance, héritage, liaison, surcharge, classe abstraite.
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.
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.
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.
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.
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).
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.
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.
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.
Sur les machines Linux de l'UFR (ippcXY), on trouve le compilateur Java de chez SUN. On utilisera les commandes suivantes :
/usr/X11R6/bin/netscape
) en indiquant la classe, sous-classe d'Applet, à charger dans une marque HTML.
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
).
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?
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.
point
par la classe pointc
contenant
un champ couleur
de type color
. La méthode d'affichage
devra tenir compte de la couleur.
point
par la classe point3D
contenant
un champs pour la 3ème dimension.
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
:
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"); } }
Affichage | Istring.read_str | PIstring.read_str |
static | ||
static | ||
static | static |
Liste
décrivant les méthodes
display
, null
, head
, tail
et len
...
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
Test
implantant la recherche des
nombres premiers selon la méthode du crible d'Erathostène.
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 ("*") ;}
}
Div
. La méthode eval
déclenche une
exception si le dénominateur vaut 0.
ExpBool
pour les expressions
booléennes.
Not
, Or
et Equal
sur les entiers.
Cond
à la hiérarchie ExpAr
pour traiter l'expression conditionnelle : x = 4 ? 7 : 19
qui retourne 7 si x
vaut 4 et 19 sinon.
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()
.
void dup()
qui duplique
le sommet de pile, ...
, void print()
qui affiche le sommet de
pile.
ContexteGraphique
pour décrire
le point courant et la couleur courante.
Environnement
contenant un champ Pile
,
un champ Contexte_graphique
.
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.
Programme
qui lit sur l'entrée standard
des expressions du langage, empile les valeurs et exécute les les instructions.
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.
Voici quelques livres parmi les très nombreux ouvrages sur Java :
Une bonne description de Java, la partie interface graphique et événement est bien présentée ainsi que la partie distribution d'applications. A conseiller.
Une bonne référence pour le noyau du langage, les API ne sont pas abordées. Gosling est un des concepteurs de Java chez SUN. A conseiller en 2ème lecture.
Le style un peu idiot agace, en particulier la comparaison systématique avec C++ a un ton propagandiste. A déconseiller.
et quelques liens locaux et distants de documentations utiles :
http://infop6.cicrp.jussieu.fr/logiciels.html
: la page des logiciels sur les machines Linux;
http://java.sun.com
: le serveur Java de SUN.
http://www.gamelan.com
: le site d'archives d'applets.
http://pausejava.u-strasbg.fr/
: la pause Java, avec une sélection d'applets, une gazette des nouveautés, ...
http://www.labri.u-bordeaux.fr/java
: le Java(tm) Cyber Club France (ce site est miroir de celui de Sun).