Eléments de Java - Université Paris Ouest Nanterre

Transcription

Eléments de Java - Université Paris Ouest Nanterre
Université Paris Ouest - Nanterre - la Défense
Master DEFI
2010 - 2011
Eléments de Java
Marcel Cori
1 Introduction
Le langage de programmation Java a été introduit par l’entreprise Sun Microsystems
en 1995. Il est très lié au développement d’Internet.
1.1
1.1.1
Quelques caractéristiques du langage Java
Java est un langage orienté-objet
Java fait partie, à la suite de SMALLTALK et C++, des langages orientés-objet, qui
sont apparus vers 1985.
Les éléments primitifs du langage sont les objets, ou plutôt les types d’objets, autrement
dit les classes.
Les programmes, écrits relativement à ces classes, sont des méthodes. Plus exactement,
les classes étant organisées selon une hiérarchie, les méthodes sont définies au niveau
adéquat de la hiérarchie.
1.1.2
Compilation et interprétation
- Le compilateur de Java engendre du code qui est indépendant de toute machine, et
donc peut être transmis par Internet.
- L’interpréteur de Java, appelé machine virtuelle (JVM), exécute le code compilé.
Rien, dans le langage, n’est dépendant d’une implémentation particulière. Par exemple,
la dimension de chacun des types primitifs est spécifiée par le langage.
1.1.3
Robustesse, sécurité
- Les déclarations doivent être explicites. La compatibilité entre les types est vérifiée
à la compilation.
- A l’exécution, on ne peut accéder aux tableaux et aux suites de caractères que dans
les bornes.
- On ne peut accéder directement à la mémoire de l’ordinateur.
1.2
1.2.1
Aspects pratiques
Définition et compilation des classes
Une classe peut être définie de la manière suivante :
public class NomDeLaClasse
{ hcorps de la classei }
2
La classe doit être stockée dans un fichier nommé NomDeLaClasse.java.
On compile par la commande :
javac NomDeLaClasse.java
Dans le cas où il y a succès de la compilation, est engendré un fichier
NomDeLaClasse.class
1.2.2
L’exécution d’un programme principal
A l’intérieur d’une classe qui a l’attribut public1 , on définit une méthode dont le nom
est main, de la manière qui suit :
public static void main(String[ ] args)
{ hcorps de la méthodei }
L’exécution s’effectue à l’aide de la commande :
java NomDeLaClasse
Par exemple, la classe qui suit doit être définie dans un fichier nommé Bonjour.java.
public class Bonjour{
public static void main(String[ ] args){
System.out.println("Bonjour tout le monde") ; } }
1.2.3
Les commentaires
En Java les commentaires peuvent s’écrire :
- sur une ligne commençant par un double slash : //· · ·
- sur un nombre quelconque de lignes, entre slash étoile et étoile slash : /* · · ·
1.2.4
*/
Les paquets
Java est donné avec un ensemble de classes prédéfinies, qui sont rangées dans des
paquets (packages). Ainsi java.awt (pour les interfaces graphiques), java.io (pour les
entrées/sorties), java.lang qui contient les éléments principaux du langage.
Par exemple BufferedReader est une classe de java.io. On doit donc écrire
java.io.BufferedReader
Mais on peut éviter cette écriture grâce à l’instruction import :
import java.io.BufferedReader ;
ou, plus généralement,
import java.io.* ;
qui s’applique à toutes les classes du paquet.
1
Voir plus loin, page 20.
3
Cette instruction est placée au tout début du fichier où est stockée une classe, avant
même la définition de la classe.
On remarquera que l’instruction
import java.lang.* ;
est implicite.
1.2.5
Les applets
Les applets sont des programmes écrits sur des pages Web, dont l’exécution est commandée
par un navigateur comme Netscape ou Internet Explorer.
Les applets ne contiennent pas de méthode main.
Un exemple d’applet est donné ci-dessous :
public class Oval extends Applet{
public void paint(Graphics g) {
g.setColor(Color.green) ;
g.fillOval(10,10,330,100) ; } }
Cet applet peut être appelé par l’ouverture du fichier HTML :
<HTML>
<HEAD>
<TITLE> Oval </TITLE>
</HEAD>
<BODY>
<P>
<APPLET code = "Oval.class" width = 380 height = 150>
</APPLET>
</BODY>
</HTML>
4
2 La syntaxe de base
2.1
2.1.1
-
-
Variables et constantes
Les types primitifs
Il y a huit types primitifs en
des types entiers : byte
short
int
long
des types réels :
float
double
le type caractère : char
le type booléen :
boolean
2.1.2
Java :
codé sur
codé sur
codé sur
codé sur
codé sur
codé sur
8 bits
16 bits
32 bits
64 bits
32 bits
64 bits
Les constantes
Les constantes booléennes sont true et false.
Les caractères constants sont notés entre “quotes” : ’a’.
Certains caractères sont notés de manière particulière :
’\n’ nouvelle ligne
’\\’ backslash
’\’’ quote
’\"’ guillemet
Les constantes de type String (non primitif) seront notées entre guillemets :
"Bonjour"
2.1.3
Les variables et leurs déclarations
Dans les identificateurs de Java, et en particulier dans les noms des variables, la
différence majuscules/minuscules est significative.
Conventionnellement, on fait commencer les noms des variables par des minuscules2 .
En Java, les déclarations ne sont pas séparées des instructions exécutables. On déclare
une variable à proximité de sa première utilisation.
La forme d’une déclaration peut être la suivante :
htypei hliste de nomsi ;
2
Les classes commenceront par des majuscules.
5
Par exemple :
char a ;
int x,y ;
2.1.4
Initialisation des variables et affectation
Une variable doit être initialisée avant sa première utilisation. Cela s’effectue à l’aide
de l’instruction d’affectation, qui a la forme générale suivante :
hnomi = hvaleuri ;
une valeur pouvant être donnée par une constante, une variable, ou une expression.
Par exemple :
x = 18 ;
y = x;
La déclaration de la variable peut être regroupée avec son initialisation. De la manière
suivante :
char lettre = ’a’, b = ’b’ ;
char l1 = lettre ;
2.2
2.2.1
Opérateurs
Les opérateurs arithmétiques
Les opérateurs arithmétiques sont “+” (pour l’addition), “-” (pour la soustraction),
“*” (pour la multiplication) et “/” (pour la division).
Ce dernier opérateur ne va pas donner le même résultat selon le type des opérandes.
Ainsi, à l’issue de l’exécution des trois instructions qui suivent
float x = 5 ;
float y = 2 ;
float quotient = x/y ;
la variable quotient va contenir la valeur 2.5, alors qu’à la suite des instructions
int x = 5 ;
int y = 2 ;
float quotient = x/y ;
la valeur contenue sera 2.0.
2.2.2
L’opérateur de conversion de types
Un type entre parenthèses placé devant une valeur convertit la valeur dans le type.
C’est ainsi que si on remplace la dernière instruction de la séquence ci-dessus par
float quotient = (float) x/y ;
on obtient encore3 pour résultat 2.5.
3
(float) x construit une valeur réelle, qui devient le premier opérande de la division, opérande qui décide du
type du résultat.
6
2.2.3
Les affectations complexes
On peut écrire
a += b ;
Cela équivaut à :
a = a + b;
De même on peut utiliser les affectations complexes “-=”, “*=” et “/=”.
L’auto-incrémentation utilise le symbole “++”. L’écriture de
++ x ;
ou de x ++ ;
équivaut à
x = x + 1;
Toutefois, on n’obtiendra pas le même résultat si on écrit
x = 3;
y = ++ x ;
ou si on écrit
x = 3;
y = x ++ ;
Dans les deux cas, on aura x = 4, mais dans le premier cas on aura y = 4, alors que dans
le deuxième cas, on aura y = 3.
De même, l’auto-décrémentation utilise le symbole “--”.
2.2.4
Les opérateurs de comparaison
Les opérateurs de comparaison sont “==” (pour l’égalité), “<” (inférieur), “<=” (inférieur
ou égal), “>” (supérieur), “>=” (supérieur ou égal), “ !=” (différent).
2.2.5
Les opérateurs logiques
Les opérateurs logiques sont “&&” ou “&” (et), “||” ou “|” (ou) et “ !” (non).
Il est plus économique d’utiliser les opérateurs “&&” ou “||” plutôt que “&” ou “|”,
et dans certains cas cela évite des erreurs à l’exécution.
En effet, dans le calcul de l’expression
y == 0 || x/y > 100
si on trouve la valeur true pour le membre gauche de la disjonction, on n’examine pas le
membre droit.
2.2.6
La priorité entre opérateurs
Les opérateurs * et / sont prioritaires par rapport aux opérateurs + et -.
Les opérateurs arithmétiques sont prioritaires par rapport aux opérateurs de comparaison.
Les opérateurs de comparaison sont prioritaires par rapport aux opérateurs logiques.
L’opérateur de négation est prioritaire par rapprot aux opérateurs ou et et.
L’opérateur et est prioritaire par rapport à l’opérateur ou.
En cas de priorités égales, les opérations sont effectuées de gauche à droite.
7
2.3
2.3.1
Les instructions conditionnelles
Les instructions if
Les deux formes de l’instruction if sont les suivantes :
if (hconditioni) hinstructioni
if (hconditioni) hinstructioni else hinstructioni
La condition est une expression qui produit un résultat de type booléen.
Un bloc peut remplacer une instruction : un bloc est composé de plusieurs instructions
qui se suivent, encadrées par des accolades.
Il est possible, dans une expression, de calculer une valeur selon le résultat d’un test.
On peut ainsi écrire :
a = b < 2 ? 1 : 5;
a prendra la valeur 1 si b est inférieur à 2, la valeur 5 sinon.
2.3.2
Les instructions itératives
On définit les instructions while, for et do.
while (hconditioni) hinstructioni
for (hinitialisationi ;htesti ; hmise à jouri)
hinstructioni
do hinstructioni while (hconditioni) ;
Les conditions sont encore des expressions qui produisent un résultat booléen. Les
instructions peuvent encore être remplacées par des blocs.
L’initialisation affecte une valeur (initiale) à au moins une variable entière. Par exemple :
i = 1.
La mise à jour incrémente ou décrémente des variables entières. Par exemple : i++.
Le test est une condition qui détermine jusqu’à quel moment on reste dans la boucle.
Par exemple : i <= n.
L’instruction break ; permet de sortir d’une itération à n’importe quel moment.
Elle permet de quitter une boucle plus extérieure si celle-ci a été repérée par une
étiquette. On écrit par exemple
hétiquettei : while ...
et, à l’intérieur de cette boucle :
break hétiquettei ;
8
L’instruction continue ; permet de repartir au début de la boucle, sans en sortir. On
peut également écrire
continue hétiquettei ;
pour repartir au début d’une boucle plus extérieure.
2.3.3
Les aiguillages
L’instruction switch permet d’exécuter conditionnellement une instruction selon une
valeur de type entier ou caractère. Par exemple :
switch(x) {
case 2 :
case 4 :
hsuite d’instructions qui seront exécutées si x vaut 2 ou 4i
break ;
case 3 :
hsuite d’instructions qui seront exécutées si x vaut 3i
break ;
default :
hsuite d’instructions qui seront exécutées si x prend une autre valeuri
break ; }
La partie default de l’aiguillage est facultative. Si elle n’est pas présente, et si les
valeurs explicitement prévues ne sont pas atteintes, aucune instruction n’est exécutée.
9
3 Objets, classes, méthodes
3.1
3.1.1
Les tableaux
Déclaration et initialisation
La déclaration d’un tableau s’effectue d’une des deux manières suivantes :
htypei [ ] hnomi ;
htypei hnomi [ ] ;
Un tableau peut être initialisé par toutes les valeurs qui le composent. Par exemple :
int [ ] tab = {5,4,3,2,1} ;
Mais on peut également créer un tableau sans en donner les valeurs initiales, de la
manière qui suit :
htypei[ ] hnomi = new htypei[hvaleuri] ;
Selon le type déclaré, les éléments du tableau seront initialisés à 0, au caractère
“espace” ou à l’objet null4 .
3.1.2
Accès aux éléments
On accède ensuite à chaque élément du tableau par :
hnomi [hvaleuri] ;
La première valeur possible renvoyant à un élément du tableau, autrement dit le
premier indice possible, est 0 : sur l’exemple ci-dessus tab[0] est initialisé à 5.
On notera que chaque fois que l’on accède à un élément du tableau, il y a une
vérification que l’indice spécifié s’inscrit bien dans les bornes prévues.
Par ailleurs, la dimension du tableau peut être obtenue par hnomi.length. Ainsi, si un
tableau tab est tel que tab.length vaut 5, on accède aux éléments de tab[0] à tab[4].
3.1.3
Tableaux à plusieurs dimensions
Un tableau à plusieurs dimensions n’est rien d’autre qu’un tableau de tableaux. C’est
ainsi que l’on peut déclarer :
int [ ] [ ] matrice = new int[5][7] ;
On accède à un élément par exemple par matrice[i][j].
4
Voir ci-après, paragraphe 3.1.5.
10
On peut également initialiser un tableau à plusieurs dimensions par des valeurs constantes.
Par exemple :
char table [ ][ ]= {{’a’,’b’,’c’},{’a’,’b’},{’a’},{ }} ;
On remarque que les tableaux à une dimension qui composent le tableau à deux
dimensions n’ont pas nécessairement tous la même dimension, et même qu’un de ces
tableaux peut avoir pour dimension 0.
On peut fixer une des dimensions du tableau, par exemple par
int matrice [ ] [ ] = new int[10] [ ] ;
et ensuite obtenir chacune des autres dimensions après calcul :
matrice[i] = new int[i++] ;
matrice.length donne la première dimension du tableau, matrice[i].length donne
une deuxième dimension, etc.
3.1.4
Les tableaux dans des listes d’arguments
Dans une liste d’arguments, un tableau apparaı̂t selon l’une des deux manières qui
suivent :
a(int [ ] tab, ...)
a(int tab[ ], ...)
C’est ainsi que l’unique argument de la méthode main est un tableau de String, auquel
nous avons donné plus haut (page 3) le nom args.
3.1.5
Les tableaux sont des références
En Java, les types de données non primitifs, c’est-à-dire les tableaux et les objets, sont
des références. C’est-à-dire que c’est l’adresse du tableau ou de l’objet qui est stockée.
La déclaration d’un objet ou d’un tableau ne libère pas d’espace en mémoire pour cet
objet, mais pour son adresse. D’où l’instruction new qui libère effectivement l’espace.
Une conséquence en est que si t1 et t2 sont deux tableaux, une instruction telle que
t1 = t2 ;
produit l’identification des deux tableaux.
Ensuite, l’écriture de
t1[2] = 5 ;
modifie aussi bien le tableau t2 que le tableau t1.
Une référence particulière est la référence null. Elle représente une adresse qui ne
réfère à rien.
11
3.2
3.2.1
Les String
Caractéristiques particulières
Les String sont des objets, mais qui partagent certaines caractéristiques avec les types
primitifs :
- il existe des String constants, comme "Bonjour" ;
- il est possible d’initialiser un String sans passer par new :
String s = "bonjour" ;
String vide = "" ;
String t = s ;
- il existe un opérateur sur les String, l’opérateur de concaténation, noté +.
Cet opérateur sera utilisé notamment pour l’écriture sur une même ligne de plusieurs
données, car System.out.println prend pour argument un String unique.
Par ailleurs, un String est non transformable (contrairement aux tableaux). En conséquence, si l’on écrit :
String a = "bon" ;
String b = a ;
a = a + "jour" ;
on obtient "bonjour" comme valeur associée à a, et "bon" comme valeur associée à b.
Cela signifie que l’on a construit une nouvelle référence pour a.
3.2.2
Les méthodes sur les String
Les méthodes s’appliquent à des classes d’objets. Ainsi les méthodes définies ci-dessous
s’appliquent aux String.
Soient a et b deux String, n et p deux nombres entiers.
a.charAt(n) fournit le (n+1)-ième caractère de a ;
a.length() fournit la longueur de a ;
a.substring(n,p) fournit le String compris entre le (n+1)-ième caractère et le
p-ième caractère de a ;
a.indexOf(b) prend une valeur de type int : une valeur négative si b n’est pas
une sous-suite de a, n-1 où n est la place de la première occurrence de b dans a sinon ;
a.equals(b) prend la valeur vrai si a et b réfèrent à des String qui sont égaux,
prend la valeur faux sinon ;
a.compareTo(b) prend une valeur de type int : une valeur négative si a précède
b dans l’ordre alphabétique, une valeur positive si b précède a, la valeur 0 si les deux
String sont égaux.
3.3
Les classes
Les classes décrivent des types d’objets :
- les attributs de ces objets, sous la forme de champs,
12
- les actions qui s’appliquent à ces objets, sous la forme de méthodes.
3.3.1
La définition d’une classe
Ci-dessous est définie la classe Etudiant, sans aucune méthode5 :
public class Etudiant{
public String nom, prenom ;
public int naissance ;
public float note1, note2 ;}
Cette classe comporte cinq champs : le nom, le prénom, l’année de naissance, ainsi que
deux notes.
3.3.2
La création et la manipulation des objets
Une fois une classe définie, on peut déclarer et créer des objets appartenant à cette
classe. Cela s’effectue grâce à l’instruction new.
Etudiant x = new Etudiant( ) ;
Les différents champs de l’objet sont initialisés à des valeurs par défaut par l’instruction
new : zéro pour les champs numériques, la valeur null pour les références, ainsi les String.
On peut accéder aux différents champs de l’objet de la manière suivante :
x.nom = "Dupont" ;
x.note1 = 15 ;
System.out.println("la note de " + x.nom + " est " + x.note1) ;
3.3.3
3.3.3.1
Les méthodes
La définition des méthodes
Considérons à nouveau la classe Etudiant, mais cette fois définie avec quatre méthodes6 .
public class Etudiant{
public String nom, prenom ;
public int naissance ;
public float note1, note2 ;
public int age(int anneeCourante)
{return anneCourante - naissance ;}
public float moyenne( )
{return note1<note2 ? note2 : (note1+note2)/2 ;}
public boolean egal(Etudiant z)
5
La classe doit être enregistrée dans un fichier Etudiant.java et compilée séparément.
On notera que l’ordre de déclaration des méthodes dans la classe n’a pas d’importance, de même que l’ordre
de déclaration des champs.
6
13
{if (nom.equals(z.nom) && prenom.equals(z.prenom)) return true ;
else return false ;}
public void imprime( )
{System.out.println(prenom + " " + nom + ", né en " + naissance) ;
if(note1 = 0 && note2 = 0) return ;
System.out.println("première note : " + note1) ;
System.out.println("deuxième note : " + note2) ;}
}
Une méthode est composée d’un en-tête et d’un corps. Pour l’instant, la forme de l’entête d’une méthode est :
public htypei hméthodei (hliste de paramètresi)
La liste de paramètres peut être vide (exemple des méthodes moyenne et imprime).
Auquel cas les parenthèses demeurent.
Le type, qui spécifie le type du résultat de la méthode, peut être remplacé par l’attribut
void qui indique que la méthode ne fournit pas de résultat.
On notera que plusieurs méthodes différentes portant le même nom peuvent être
définies. C’est la signature de la méthode, c’est-à-dire la liste de types des paramètres, qui
fait la différence.
Le corps de la méthode est constitué d’un bloc d’instructions, entre accolades. Ce bloc
doit contenir l’instruction
return hvaleuri ;
la valeur étant du type défini pour le résultat de la méthode.
Si la méthode est définie avec l’attribut void, l’instruction est simplement
return ;
et elle n’est pas obligatoire. Elle permet simplement de sortir de la méthode avant la
dernière instruction (exemple de la méthode imprime).
3.3.3.2
L’appel des méthodes
Ci-dessous une suite d’instructions qui permettent d’initialiser un objet de type
Etudiant et d’accéder aux méthodes définies dans la classe Etudiant.
Etudiant x = new Etudiant( ) ;
x.nom = "Dupont" ;
x.prenom = "Jean" ;
x.naissance = 1972 ;
x.note1 = 15 ;
x.note2 = 9 ;
if x.moyenne( ) >= 10. System.out.println("l’étudiant est reçu"") ;
else System.out.println("l’étudiant est collé"") ;
14
int a = 2004 ;
System.out.println("^
age : " + x.age(a)) ;
L’appel des méthodes s’effectue “par valeurs”. Cela signifie que les arguments qui sont
d’un des types primitifs ne peuvent être altérés par l’exécution d’une méthode.
3.3.4
Les constructeurs
L’instruction new fait appel à une méthode qui a le même nom qu’une classe. Cette
méthode est un constructeur.
Le constructeur peut être défini implicitement, par la définition de la classe, mais on
peut aussi écrire explicitement des constructeurs. Par exemple :
public class Etudiant{
public String nom, prenom ;
public int naissance ;
public float note1, note2 ;
public Etudiant(String a, String b)
{this.nom = b ;
this.prenom = a ;}
}
this désigne l’objet de la classe que l’on construit. Les champs non initialisés par le
constructeur prennent une valeur par défaut7 .
Le constructeur est une méthode qui ne comporte pas d’instruction return (bien que
n’ayant pas l’attribut void).
L’appel du constructeur peut alors s’effectuer de la manière suivante :
Etudiant x = new Etudiant("Jean","Dupont") ;
Une même classe peut posséder plusieurs constructeurs :
public class Etudiant{
public String nom, prenom ;
public int naissance ;
public float note1, note2 ;
public Etudiant()
{ }
public Etudiant(String a, String b)
{this.nom = b ;
this.prenom = a ;}
public Etudiant(String a)
{int i ;
7
C’est ce qui se produit pour tous les champs quand on a un constructeur implicite.
15
for (i=1 ; i <= a.length() ; i++)
if (a.charAt(i) == ’ ’) break ;
this.nom = a.substring(i,a.length()) ;
this.prenom = a.substring(0,i) ;}
public Etudiant(Etudiant x)
{this.nom = x.nom ;
this.prenom = x.prenom ;
this.naissance= x.naissance ;
this.note1 = x.note1 ;
this.note2 = x.note2 ;}
}
Différents appels des constructeurs sont alors possibles :
Etudiant
Etudiant
Etudiant
Etudiant
e1
e2
e3
e4
=
=
=
=
new
new
new
new
Etudiant("Jean","Dupont") ;
Etudiant("Jean Dupont") ;
Etudiant() ;
Etudiant(e3) ;
C’est encore la signature des constructeurs qui permet au système de faire la différence.
Quand un constructeur explicite est défini, on ne peut plus utiliser le constructeur
implicite, à moins de le redéfinir (comme c’est le cas ci-dessus).
On remarquera enfin qu’on peut initialiser un objet de la classe Etudiant à partir
d’un autre objet de la même classe (dernier constructeur défini).
3.3.5
3.3.5.1
Variables et méthodes de classe
Les variables de classe
Il est possible d’attacher une variable à une classe donnée. On dit alors qu’il s’agit
d’une variable de classe.
Une variable de classe est déclarée avec l’attribut static. Ainsi :
public class Etudiant{
public String nom, prenom ;
public float note1, note2 ;
static int nbRecus = 0 ;
public Etudiant(String a, String b, float n1, float n2)
{this.nom = a ;
this.prenom = b ;
this.note1 = n1 ;
this.note2 = n2 ;
if (this.moyenne() >= 10) nbRecus++ ;}
public float moyenne( )
16
{return note1<note2 ? note2 : (note1+note2)/2 ;}
}
A chaque fois qu’on appelle le constructeur de la classe, la variable de classe est
incrémentée. On accède ensuite à la valeur de cette variable à travers la classe, par une
instruction telle que :
System.out.println("Nombre d’étudiants reçus = " + Etudiant.nbRecus) ;
Remarque : System est une classe ; out est une variable de cette classe, de type PrintStream ;
println est une méthode définie pour les objets de type PrintStream.
Cela explique la forme donnée à l’instruction d’écriture que nous avons employée
jusqu’à présent.
3.3.5.2
Les constantes de classe
A un nom, il peut être associé dans une classe une valeur qui ne pourra ensuite être
modifiée. C’est l’attribut final qui indique que la valeur ne sera pas modifiée :
public static final int annee = 2004 ;
3.3.5.3
Les méthodes de classe
Il y a également des méthodes de classe qui se distinguent des méthodes d’instance.
Pour le comprendre, considérons les deux méthodes définies ci-dessous à l’intérieur de la
classe Personne.
public class Personne{
public String nom ;
public int naissance ;
public Personne plusVieux(Personne x) ;
{return this.naissance > x.naissance ? x : this ;}
public static Personne plusVieux(Personne a, Personne b) ;
{return a.naissance > b.naissance ? b : a ;}
}
Pour deux variables p1 et p2 de type Personne, que l’on suppose intialisées, l’appel
des méthodes s’effectue comme suit :
Personne p = p1.plusVieux(p2) ;
Personne q = Personne.plusVieux(p1,p2) ;
Les deux appels vont produire ici le même résultat. La méthode d’instance est appelée
à travers une instance de la classe, la méthode de classe à travers le nom de la classe.
17
Il existe des méthodes de classe prédéfinies. Ainsi, Math.sqrt(x) va donner la racine
carrée de la valeur réelle x.
3.4
3.4.1
3.4.1.1
Phénomènes d’héritage
Les sous-classes
L’extension d’une classe
Les étudiants peuvent être considérés comme des personnes particulières. L’extension
d’une classe revient à définir une sous-classe. En effet, en ajoutant des champs et des
méthodes supplémentaires à une classe donnée, on construit une classe plus spécifique.
Pour indiquer qu’une classe est l’extension d’une autre classe, on utilise le mot clé
extends. Ainsi :
public class Personne{
public String nom, prenom ;
public int naissance ;
public Personne()
{ }
public Personne(String a, String b)
{this.nom = b ;
this.prenom = a ;}
public int age(int anneeCourante)
{return anneCourante - naissance ;}
public boolean egal(Personne z)
{if (nom.equals(z.nom) && prenom.equals(z.prenom)) return true ;
else return false ;}
}
public class Etudiant extends Personne{
public float note1, note2 ;
public float moyenne( )
{return note1<note2 ? note2 : (note1+note2)/2 ;}
public void imprime( )
{System.out.println(prenom + " " + nom + ", né en " + naissance) ;
if(note1 = 0 && note2 = 0) return ;
System.out.println("première note : " + note1) ;
System.out.println("deuxième note : " + note2) ;}
}
On remarque que les champs et méthodes sont indiqués au niveau adéquat de la
hiérarchie, la classe Etudiant héritant des champs et des méthodes de la classe Personne.
La séquence d’instructions ci-dessous est alors possible :
18
Etudiant
String n
Personne
int nn =
e = new Etudiant() ;
= e.nom ;
p = e;
e.age(2004) ;
En revanche, l’appel de p.note1 ne serait pas possible.
On notera qu’en fait toute classe est sous-classe d’une autre classe. Si une classe n’est
pas définie explicitement comme l’extension d’une autre classe, elle sera considérée comme
sous-classe de la classe prédéfinie Object.
La hiérarchie des classes selon la relation d’extension forme un arbre, dont la racine
est la classe Object.
Remarque : Deux champs peuvent être déclarés dans la classe et dans son extension (par
exemple le champ numero peut représenter le numéro national d’identification pour la
classe Personne et le numéro d’étudiant pour la classe Etudiant). En ce cas, on accède
à l’un ou l’autre des champs selon le type d’objet en cause.
3.4.1.2
L’écriture des constructeurs
Il est intéressant de faire appel à un constructeur de la classe pour définir le constructeur
de son extension. On utilise à cette fin le mot clé super. Par exemple ci-dessous dans la
définition d’un constructeur de la classe Etudiant :
public class Etudiant extends Personne{
public float note1, note2 ;
public Etudiant(String a, String b, int n, float n1, float n2)
{super(a,b,n) ;
this.note1 = n1 ;
this.note2 = n2 ;}
}
super ne peut être utilisé qu’en tant que première instruction d’un constructeur.
Si on n’utilise pas super, tout se passe en fait comme si on appelait implicitement
super( ).
3.4.1.3
Le recouvrement des méthodes
Quand une méthode associée à une sous-classe a le même nom, la même signature et
le même type de résultat qu’une autre méthode associée à la classe qu’elle étend, il y a
recouvrement des méthodes. Cela correspond à une exception à l’héritage des propriétés.
Ainsi :
public class Etudiant extends Personne{
public float note1, note2 ;
19
public float moyenne( )
{return note1<note2 ? note2 : (note1+note2)/2 ;}
}
public class EtudiantTravailleur extends Etudiant{
public float moyenne( )
{return note1<note2 ? note2 : note1 ;}
}
La moyenne n’est pas calculée de la manière habituelle pour les étudiants-travailleurs.
Il est toutefois possible de calculer à l’intérieur de la classe EtudiantTravailleur la
moyenne obtenue pour un étudiant ordinaire, en utilisant encore le mot clé super :
public class EtudiantTravailleur extends Etudiant{
public float moyenne( )
{return note1<note2 ? note2 : note1 ;}
public float moyenneEtudiant( )
{return super.moyenne( ) ;}
}
3.4.2
3.4.2.1
Dissimulation de données
Modificateurs de visibilité
Les variables internes aux classes peuvent être déclarées avec l’attribut private au
lieu de public.
Un champ qui a l’attribut private est uniquement visible dans les méthodes définies
à l’intérieur de la classe.
Il existe deux autres niveaux de visibilité :
- si un champ a l’attribut protetcted, il est visible à l’intérieur de la classe où le
champ est défini, ainsi que dans toutes les sous-classes et dans toutes les classes qui sont
dans le même paquet ;
- si un champ n’a aucun des attributs private, protetcted ou public, il est visible
uniquement dans le paquet où il est défini. Pas dans les sous-classes qui sont dans un
autre paquet.
3.4.2.2
Méthodes d’accès aux données
Pour accéder à des données qui sont dissimulées, il faut écrire des méthodes spécifiques.
Si par exemple on décide de dissimuler les notes dans la classe Etudiant afin de ne
pas risquer d’obtenir des valeurs hors norme, on ne peut plus initialiser ou modifier les
valeurs des notes autrement que par l’intermédiaire d’un constructeur ou d’une méthode
spécifique, et on ne peut connaı̂tre les notes qu’à travers des méthodes spécifiques.
public class Etudiant{
20
public String nom, prenom ;
private float note1, note2 ;
private boolean testNote(float n)
{return n <= 20. && n >= 0. ;}
//Constructeur public vérifiant la conformité des notes
public Etudiant(String a, String b, float n1, float n2)
{this.nom = b ;
this.prenom = a ;
if (testNote(n1)) this.note1 = n1 ;
else this.note1 = 10. ;
if (testNote(n2)) this.note2 = n2 ;
else this.note2 = 10. ;}
//Méthodes d’accès aux données
public float quelleNote1( )
{return note1 ;}
public float quelleNote2( )
{return note2 ;}
//Méthodes permettant de modifier les données
public void changeNotes(float n1, float n2)
{if testNote(n1)) this.note1 = n1 ;
if testNote(n2)) this.note2 = n2 ;}
public void incrementeNotes(float inc)
{note1 += inc ;
if (note1 > 20.) note1 = 20. ;
note2 += inc ;
if (note2 > 20.) note2 = 20. ;}
}
Dans un programme qui fait appel à un objet de type Etudiant, pour accéder aux
notes on écrit x.quelleNote1( ) au lieu de x.note1, ce qui interdit de modifier le champ
concerné8 .
3.4.3
3.4.3.1
Classes abstraites et interfaces
Classes abstraites
Supposons que nous ayons à représenter des étudiants de maı̂trise et des étudiants de
licence, et qu’il y ait à calculer une moyenne pour les uns et les autres, mais pas de la
même façon.
Il peut en ce cas être intéressant de définir une classe Etudiant, pour laquelle la métode
moyenne soit définie “abstraitement”, c’est-à-dire sans instructions exécutables.
La classe est dite classe abstraite. Elle peut contenir des méthodes abstraites et non
abstraites, mais seules les classes abstraites peuvent contenir des méthodes abstraites.
8
En effet, x.quelleNote1( ) ne peut par exemple pas apparaı̂tre dans la partie gauche d’une affectation.
21
La classe abstraite Etudiant admettra alors deux sous-classes, EtudiantDeMaitrise
et EtudiantDeLicence9 .
Une classe abstraite ne peut être instanciée directement : seules ses sous-classes non
abstraites peuvent être instanciées.
3.4.3.2
Méthodes abstraites
Considérons la classe abstraite Etudiant, accompagnée de deux méthodes abstraites.
Puis les sous-classes (non abstraites) EtudiantDeMaitrise et EtudiantDeLicence.
public abstract class Etudiant extends Personne{
public Etudiant(String a, String b)
{super(a,b) ;}
public abstract float moyenne( ) ;
public abstract boolean estRecu( ) ;
}
public class EtudiantDeMaitrise extends Etudiant{
public float cours, memoire, stage ;
public EtudiantDeMaitrise(String a, String b, float c, float m, float s)
{super(a,b) ;
this.cours = c ;
this.memoire = m ;
this.stage = s ;}
public float moyenne( )
{return (cours * 5 + memoire * 6 + stage * 2) / 13 ;}
public boolean estRecu( )
{if (cours == 0 || memoire == 0 || stage == 0) return false ;
return moyenne( ) >= 0 ? true : false ;}
}
public class EtudiantDeLicence extends Etudiant{
public float cours1, cours2 ;
public EtudiantDeLicence(String a, String b, float c1, float c2)
{super(a,b) ;
this.cours1 = c1 ;
this.cours2 = c2 ;}
public float moyenne( )
{return (cours1 + cours2) / 2 ;}
public boolean estRecu( )
{if (cours1 >= 10 && cours2 >= 10) return true ;
else return false ;}
9
Une classe abstraite peut admettre des sous-classes abstraites et des sous-classes non abstraites.
22
}
On peut ensuite définir un tableau d’étudiants, qui contiendra indifféremment des
étudiants de maı̂trise et des étudiants de licence. Et ensuite faire appel aux méthodes
moyenne et estRecu pour des éléments de ce tableau :
Etudiant [ ] tab = new Etudiant[30] ;
tab[0] = new EtudiantDeLicence("Anne","Dubois",12.,8.5) ;
tab[1] = new EtudiantDeMaitrise("Luc","Martin",10.,14.,15.) ;
···
int nb = 0 ;
for (int i = 0 ; i < tab.length ; i++)
if(tab[i].estRecu( )) nb++ ;
System.out.println("le nombre des reçus est " + nb) ;
On notera que la sous-classe non abstraite d’une classe abstraite doit donner un corps
à toutes les méthodes abstraites des classes abstraites dont elle hérite.
3.4.3.3
Interfaces
En Java, il ne peut y avoir d’héritage multiple : une sous-classe ne peut être l’extension
de plusieurs classes.
Il existe toutefois des interfaces, qui ne sont pas vraiment des classes : contrairement
aux classes abstraites, on ne peut y définir que des méthodes abstraites.
Toutes les variables d’une interface doivent avoir les attributs static et final, c’està-dire que ça doit être des constantes.
Une classe peut implémenter une interface, de la même manière qu’elle est l’extension
d’une classe (abstraite ou non)10 .
Mais la classe doit implémenter toutes les méthodes abstraites de l’interface. Par
exemple :
public interface Boursier
{public float montantBourse( ) ;}
public class EtudiantDeLicenceBoursier extends EtudiantDeLicence
implements Boursier {
public EtudiantDeLicenceBoursier(String a, String b, float n1, float n2)
{super(a,b,n1,n2) ;}
public float montantBourse( )
{return (float) (1000.+cours1*100+cours2*150) ;}
}
10
Une interface peut être l’extension d’une autre interface, mais pas d’une classe.
23
public class EtudiantDeMaitriseBoursier extends EtudiantDeMaitrise
implements Boursier {
public EtudiantDeMaitriseBoursier(String a, String b, float c, float m,
float s)
{super(a,b,c,m,s) ;}
public float montantBourse( )
{return (float) (1200.+cours*100+memoire*150+stage*80) ;}
}
On peut ensuite définir un tableau d’étudiants et un tableau de boursiers, et affecter
aux cases des différents tableaux les mêmes objets, comme suit :
Etudiant[ ] te = new Etudiant[20] ;
Boursier[ ] tb = new Boursier[30] ;
EtudiantDeLicenceBoursier elb =
new EtudiantDeLIcenceBoursier("Luc","Dubois",8,14) ;
te[0] = elb ;
tb[0] = elb ;
Ce qui permet de calculer tb[0].montantBourse( ), mais pas te[0].montantBourse( ).
En revanche, on pourra calculer te[0].moyenne( ).
24
4 Compléments
4.1
4.1.1
Les exceptions
Introduction
Les exceptions sont des événements “anormaux” qui sont susceptibles de se produire
à l’exécution d’un programme. Notamment des événements qui pourraient provoquer des
erreurs à l’exécution, et qui sont indétectables à la compilation.
Par exemple : une division par zéro, un indice hors des bornes d’un tableau ou la
tentative d’ouvrir un fichier inexistant.
Le programmeur peut déterminer quoi faire lorsqu’une exception se produit. Sinon,
l’exception produira l’arrêt de l’exécution du programme.
4.1.2
La classe des exceptions
Les exceptions sont des objets. Il existe une classe, la classe java.lang.Throwable,
qui admet les deux sous-classes Error et Exception.
Les Error sont des événements anormaux pour lesquels il n’est pas possible de définir
de traitement : s’ils se produisent, l’exécution du programme s’arrête obligatoirement.
On peut citer les classes d’exceptions courantes :
- ArithmeticException : pour un nombre entier trop grand, ou une division entière
par 0 ;
- IndexOutOfBoundsException : pour un indice illégal dans un tableau ou un String.
4.1.3
Le traitement des exceptions
Le traitement des exceptions s’effectue par l’instruction try/catch/finally.
try délimite un bloc d’instructions dans lequel il est possible qu’une exception se
produise.
Le bloc try est suivi par zéro, une ou plusieurs clauses catch.
La syntaxe de catch est la suivante :
catch(hclassei hnomi)
{hliste d’instructionsi}
La classe indiquée peut être n’importe quelle sous-classe de la classe Throwable.
public class Essai{
public static void main(String[ ] args)
{int maj = ? ? ;
int[ ] tab = {25,24,20,12,15} ;
25
try{for (int i=4 ; i>=0 ; i--)
System.out.println(tab[i+maj]/i) ;}
catch(ArithmeticException e)
{System.out.println("faut pas diviser par zéro") ;
System.out.println(e) ;}
catch(Exception e)
{System.out.println("il y a un problème") ;
System.out.println(e) ;}}
}
Quand un événement anormal se produit dans un bloc try, le système cherche le catch
situé dans le plus petit bloc englobant qui porte sur un événement de ce type.
Les instructions définies dans catch sont exécutées, puis on sort du bloc contenant la
combinaison try/catch.
Ici, selon la valeur affectée à maj, on sortira du bloc try parce que l’indice du tableau
sera hors des marges, ou à cause d’une division par zéro11 . Ainsi, si maj = 0, on obtient :
3
4
10
24
faut pas diviser par zéro
java.lang.ArithmeticException : / by zero
alors que si maj = 2, on a :
il y a un problème
java.lang.ArrayIndexOutOfBoundsException
Les blocs catch peuvent être suivis par un bloc finally qui donne des instructions
qui seront exécutées après la sortie du bloc try, quelle que soit la façon dont on sortira
de ce bloc.
4.1.4
La déclaration des exceptions
Certaines exceptions12 doivent être traitées, sans quoi cela produit une erreur à la
compilation.
Il faut donc écrire un catch qui en définit le traitement.
Mais il est également possible de signaler, dans l’en-tête d’une méthode, que de telles
exceptions peuvent s’y produire, afin de renvoyer le traitement de l’exception au bloc qui
appelle la méthode.
Cela s’effectue à l’aide du mot clé throws. Par exemple :
public void ouvreFichier( ) throws IOException
11
Remarquons l’ordre des clauses catch : l’exception la plus générale est placée après la plus spécifique, sinon
celle-ci ne serait jamais atteinte.
12
Mais pas les IndexOutOfBoundsException.
26
4.1.5
La définition d’exceptions
Le programmeur peut définir lui-même des exceptions, comme étant des sous-classes
de la classe Exception. Par exemple :
public class MonException extends Exception{
public MonException( )
{super( ) ;}
public MonException(String s)
{super(s) ;} }
L’exception définie, pour l’instant, ne réfère pas à un événement particulier. Cela est
effectuée ci-dessous à l’aide de l’instruction throw.
On montre également comment peut être traitée l’exception définie.
public class Essai{
public static void main(String[ ] args)
{int j = 1 ;
try{test(j) ;
System.out.println("premier passage") ;
j-- ;
test(j) ;
System.out.println("deuxième passage") ;}
catch(MonException e)
{System.out.println(e) ;}
System.out.println("troisième passage") ;
try{j-- ;
test(j) ;
System.out.println("quatrième pasage")}
catch(MonException e)
{System.out.println(e) ;}}
public static void test(int i) throws MonException
{if (i==0) throw new MonException("zéro") ;
if (i<0) throw new MonException("valeur négative") ;}
}
En ce cas, on obtiendra comme sortie du programme :
premier passage
MonException : zéro
troisième passage
MonException : valeur négative
27
4.2
4.2.1
Les entrées et sorties
Introduction
La classe System contient les constantes in, qui représente le flot d’entrée standard et
qui est de type InputStream, et out, qui représente le flot de sortie standard et qui est
de type PrintStream.
Si on ne se limite pas aux instructions d’entrée et sortie tout à fait élémentaires, il
faut importer le paquet java.io.
4.2.2
Les sorties
La classe PrintStream contient les méthodes print(x) et println(x) où x est nécessairement de type String.
La conversion des types s’effectue automatiquement pour les types primitifs. Elle fait
appel à la méthode toString pour les objets, à condition que cette méthode ait été définie
pour le type d’objet en question.
4.2.3
Les entrées
La méthode readLine( ) est de type String. Elle s’applique à la classe BufferedReader.
Elle nécessite un traitement de l’exception IOException.
On lit une ligne unique, qui est placée dans une variable de type String.
L’objet de type BufferedReader sera construit à partir d’un objet de type Reader,
plus précisément de type InputStreamReader.
Lequel sera créé à partir de l’objet System.in (de type InputStream). Cela donne :
try{
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in)) ;
String s = bf.readLine( ) ;}
catch(Exception e)
{System.out.println(e) ;}
Ensuite, on doit user des méthodes appropriées afin de convertir le String dans le type
adéquat. Par exemple, si on veut obtenir un nombre entier, on passe par la méthode de
classe Integer.parseInt, comme suit :
int x = Integer.parseInt(s) ;
Dans le cas où on lit plusieurs entités sur une même ligne, on utilisera la classe d’objets
StringTokenizer qui est dans le paquet java.util.
String x ;
StringTokenizer st = new StringTokenizer(s) ;
n = st.countTokens( ) ;
28
for (int i = 0 ; i < n ; i++)
{x = st.nextToken( ) ;
htraitement sur xi}
4.2.4
Les fichiers séquentiels
On construit un objet de type BufferedReader à partir d’un objet de type FileReader,
qui lui-même sera construit à partir du nom du fichier.
String s
try{ FileReader f = new FileReader("fichier1") ;
BufferedReader br new BufferedReader(f) ;
while ((s=br.readLine( )) != null)
system.out.println(s) ;}
catch(Exception e){System.out.println(e) ;}
Cette fraction de programme lit les unes à la suite des autres toutes les lignes d’un
fichier, et les affiche à l’écran.
Inversement, le programme ci-dessous va écrire sur un fichier des caractères donnés
sous la forme d’un tableau de codes ASCII.
import java.io.* ;
import java.util.* ;
public class Essai
{public static void main(String[ ] args)
{byte b[ ] = {45,52,63,74,85} ;
try{FileOutputStream g = new FileOutputStream("baba") ;
g.write(b) ;
byte c[ ] = {10,13,90,91,92,93} ;
g.write(c) ;}
catch(Exception e){System.out.println(e) ;}
}
}
29