|
|
|
|
|
import java.io.*; import java.lang.*; import java.util.*; class CarreMagique { final static int N = 100; static int[ ][ ] a = new int[N][N]; static void init (int n){ for (int i = 0 ; i < n; ++i) for (int j = 0; j < n; ++j) a[i][j] = 0; } static void magique (int n) { int i = n - 1; int j = n / 2; for (int k = 1; k <= n * n; ++k) { a[i][j] = k; if ((k % n) == 0) i = i - 1; else { i = (i + 1) % n; j = (j + 1) % n; } } } static void erreur (String s) { System.err.println ("Erreur fatale: " + s); System.exit (1); } static int lire () { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); System.out.print ("Taille du carré magique, svp?:: "); int n; try { n = Integer.parseInt (in.readLine()); } catch (IOException e) { n = 0; } catch (ParseException e) { n = 0; } if ((n <= 0) || (n > N) || (n % 2 == 0)) erreur ("Taille impossible."); return n; } static void imprimer (int n) { for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) System.out.print (leftAligned(5, a[i][j] + " ")); System.out.println (); } } static String leftAligned (int size, String s) { StringBuffer t = new StringBuffer (s); for (int i = s.length(); i < size; ++i) t = t.append(" "); return new String (t); } public static void main (String[ ] args) { int n = lire(); init(n); // inutile, mais pédagogique! magique(n); imprimer(n); } } |
CarreMagique
. Auparavant nous avons quelques directives sur un
nombre de classes standard dont nous allons nous servir sans utiliser
la notation longue (cf plus loin). Chaque classe contient un certain
nombre de déclarations de variables et de fonctions ou procédures. (En
programmation objet, on parle de méthodes au lieu de fonctions
ou de procédures. Ici nous utiliserons les deux terminologies). Une
des fonctions, conventionnellement de nom main
, est le point de
départ du programme. Ignorons ses arguments pour le moment.main
lit l'entier n à la
console, initialise la matrice a avec des zéros, puis calcule un
carré magique d'ordre n et l'imprime. Nous regardons maintenant les
autres fonctions et procédures. Remarquons que les commentaires sont
compris entre les séparateurs /*
et */
, ou après
//
comme en C++.CarreMagique
commence par la déclaration de trois
variables. La première déclaration définit une constante N
entière (integer en anglais, int
en abrégé) qui
représente la taille maximale du carré autorisé. Il est fréquent
d'écrire les constantes avec des majuscules uniquement. Nous adoptons
la convention suivante sur les noms: les noms de constantes ou de
classes commencent par des majuscules, les noms de variables ou de
fonctions commencent par une minuscule. On peut ne pas respecter cette
convention, mais cela rendra les programmes moins lisibles. (Pour le
moment, nous n'essayons pas de comprendre les mots clés
final
ou static
--- ce deuxième mot-clé reviendra très
souvent dans nos programmes).N
, on déclare un tableau a
d'entiers à deux
dimensions (la partie écrite avant le symbole =
) et on alloue
un tableau N × N d'entiers qui sera sa valeur. Cette
déclaration peut sembler très compliquée, mais les tableaux adoptent
la syntaxe des objets (que nous verrons plus tard) et cela permettra
d'initialiser les tableaux par d'autres expressions. Remarquons que
les bornes du tableau ne font pas partie de la déclaration. Enfin, une
troisième déclaration dit qu'on se servira d'une variable entière
n
, qui représentera l'ordre du carré magique à calculer.
CarreMagique
, nous n'avons plus que des
définitions de fonctions. Considérons la première init
, pas
vraiment utile, mais intéressante puisque très simple. Le type
void
de son résultat est vide, il est indiqué avant la
déclaration de son nom, comme pour les variables ou les tableaux. (En
Pascal, on parlerait de procédure). Elle a un seul paramètre entier
n
(donc différent de la variable globale définie auparavant).
Cette procédure remet à zéro toute la matrice a. Remarquons qu'on
écrit a[i][j]
pour son élément ai,j (0 £ i,j < n), et
que le symbole d'affectation est =
comme en C ou en Fortran
(l'opérateur = pour le test d'égalité s'écrit ==
). Les
tableaux commencent toujours à l'indice 0. Deux boucles imbriquées
permettent de parcourir la matrice. L'instruction for
a trois
clauses: l'initialisation, le test pour continuer à itérer et
l'incrémentation à chaque itération. On initialise la variable fraîche
i à 0, on continue tant que i < n, et on incrémente i de 1 à
chaque itération (++
et --
sont les symboles
d'incrémentation et de décrémentation).imprimer
. Elle a la même structure,
sauf que nous imprimons chaque élément sur 5 caractères cadrés à
gauche. Les deux fonctions de librairie System.out.print
et
System.out.println
permettent d'écrire leur paramètre (avec un
retour à la ligne dans le cas de la deuxième). Le paramètre est
quelconque et peut même ne pas exister, c'est le cas ici pour
println
. Il est trop tôt pour expliquer le détail de
leftAligned
, introduit ici pour rendre l'impression plus jolie,
et supposons que l'impression est simplement déclenchée parSystem.out.print (a[i][j] + " "); |
a[i][j] + " "
? A gauche de l'opérateur
+
, nous avons l'entier ai,j et à droite une chaîne de
caractères! Il ne s'agit bien sûr pas de l'addition sur les entiers,
mais de la concaténation des chaînes de caractères. Donc +
transforme son argument de gauche dans la chaîne de caractères qui le
représente et ajoute au bout la chaîne " "
contenant un espace
blanc. La procédure devient plus claire. On imprime tous les éléments
ai,j séparés par un espace blanc avec un retour à la ligne en fin
de ligne du tableau. La fonction compliquée leftAligned
ne fait
que justifier sur 5 caractères cette impression (il n'existe pas
d'impression formattée en Java). En conclusion, on constate que
l'opérateur +
est surchargé, car il a deux sens en Java:
l'addition arithmétique, mais aussi la concaténation des chaînes de
caractères dès qu'un de ses arguments est une chaîne de
caractères. C'est le seul opérateur surchargé (contrairement à C++ qui
autorise tous ses opérateurs à être surchargés).erreur
prend comme argument la chaîne de
caractères s
et l'imprime précédée d'un message insistant sur
le coté fatal de l'erreur. Ici encore, on voit l'utilisation de
+
pour la concaténation de deux chaînes de caractères. Puis, la
procédure fait un appel à la fonction système exit
qui arrête l'exécution du programme avec un code
d'erreur (0 voulant dire arrêt normal, tout autre valeur un arrêt
anormal). (Plus tard, nous verrons qu'il y a bien d'autres manières de
générer un message d'erreur, avec les exceptions ou les erreurs
pré-définies).lire
qui n'a pas d'arguments retourne un entier lu
à la console. La fonction commence par la déclaration d'une variable
entière n
qui contiendra le résultat retourné. Puis, une ligne
cryptique (à apprendre par coeur) permet de dire que l'on va faire une
lecture (avec tampon) à la console. On imprime un message (sans retour
à la ligne) demandant de rentrer la taille voulue pour le carré
magique, et on lit par readLine
une chaîne de caractère entrée
à la console. Cette chaîne est convertie en entier par la fonction
parseInt
et le tout est rangé dans la variable n
. Si une
erreur se produit au cours de la lecture, on récupère cette erreur et
on positionne n
à zéro. L'instruction try
permet de
délimiter un ensemble d'instruction où on pourra récupérer une
exception par un catch
qui spécifie les exceptions attendues et
l'action à tenir en conséquence. Tous les langages modernes ont
un système d'exceptions, qui permet de séparer le traitement des
erreurs du traitement normal d'un groupe d'instructions. Notre
fonction lire
finit par tester si 0 £ n < N et si n est
impair (c'est à dire n mod2 ¹ 0). Enfin, la fonction renvoie
son résultat.
else
ferait hurler tout compilateur Pascal. En Java comme en
C, le point-virgule fait partie de l'instruction. Simplement toute
expression suivie de
point-virgule devient une instruction. Pour composer plusieurs
instructions en séquence, on les concatène entre des accolades comme
dans la deuxième alternative du if
ou dans l'instruction
for
).main
. En effet,
l'argument de main
est un tableau de chaînes de caractères, qui
sont les différents arguments pour lancer le programme Java sur la
ligne de commande. Alors on supprimerait lire
et main
deviendrait:
public static void main (String[ ] args) { if (args.length < 1) erreur ("Il faut au moins un argument."); n = Integer.parseInt(args[0]); init(n); // inutile, mais pédagogique! magique(n); imprimer(n); } |
|
|
+
, -
, *
. Certains identificateurs ne
peuvent être utilisés pour des noms de variables ou procédures, et sont
réservés pour des mots clés de la syntaxe, comme class
,
int
, char
, for
, while
, ....
|
byte
, short
, int
ou
long
, selon qu'on représente ces entiers signés sur 8, 16, 32
ou 64 bits. On utilise principalement int
, car toutes les
machines ont des processeurs 32 bits, et bientôt 64 bits. Attention:
il y a 2 conventions bien spécifiques sur les nombres entiers: les
nombres commençant par 0x
sont des nombres hexadécimaux. Ainsi
0xff
vaut 255. De même, sur une machine 32 bits,
0xffffffff
vaut -1. Les constantes entières longues sont de la
forme 1L
, -2L
. Les plus petites et plus grandes valeurs
des entiers sont Integer.MIN_VALUE
= -231,
Integer.MAX_VALUE
= 231 -1; les plus petites et plus
grandes valeurs des entiers longs sont Long.MIN_VALUE
=
-263, Long.MAX_VALUE
= 263 -1; les plus petites et
plus grandes valeurs des entiers sur un octet sont
Byte.MIN_VALUE
= -128, Byte.MAX_VALUE
= 127; etc.float
ou double
. Ce sont des nombres
flottants en simple ou double précision. Les constantes sont en notation
décimale 3.1416
ou en notation avec exposant 31.416e-1
et
respectent la norme IEEE 754. Par défaut les constantes sont prises en double
précision, 3.1416f
est un flottant en simple précision. Les valeurs
minimales et maximales sont Float.MIN_VALUE
et
Float.MAX_VALUE
. Il existe aussi les constantes de la norme IEEE,
Float.POSITIVE_INFINITY
, Float.NEGATIVE_INFINITY
,
Float.NaN
, etc.boolean
. Les constantes booléennes sont
true
et false
.char
. Les constantes sont écrites entre
apostrophes, comme 'A'
, 'B'
, 'a'
, 'b'
,
'0'
, '1'
, ' '
. Le caractère apostrophe se note
'\''
, et plus généralement il y a des conventions pour des caractères
fréquents, '\n'
pour newline, '\r'
pour retour-charriot,
'\t'
pour tabulation, '\\'
pour \
. Attention: les
caractères sont codés sur 16 bits, avec le standard international Unicode.
On peut aussi écrire un caractère par son code '\u0'
pour le
caractère nul (code 0)."Pierre et Paul"
. On peut mettre des caractères spéciaux à
l'intérieur, par exemple "Pierre\net\nPaul\n"
qui s'imprimera sur
trois lignes. Pour mettre un guillemet dans une chaîne, on écrit
\"
. Si les constantes de type chaînes de caractères ont une syntaxe
spéciale, la classe String
des châines de caractères n'est pas un
type primitif.
final static int BLEU = 0, BLANC = 1, ROUGE = 2; int c = BLANC; |
|
|
+
, -
, *
, /
, et
\prog
<|, <=
, ==
et !=
pour faire des comparaisons (le
dernier signifiant ¹). Plus intéressant, les opérateurs &&
et
||
permettent d'évaluer de la gauche vers la droite un certain nombre
de conditions (en fait toutes les expressions
s'évaluent de la gauche vers la droite à la différence de C ou de Caml dont
l'ordre peut être laissé à la disposition de l'optimiseur). Une expression
logique (encore appelée booléenne) peut valoir true
ou
false
. La négation est représentée par l'opérateur !
. Ainsi(i < N) && (a[i] != '\n') && !exception |
true
si i <
N et si a[i] ¹ newline et si
exception = false. Son résultat sera false
dès
que i ³ N sans tester les autres prédicats
de cette conjonction, ou alors si i < N et
a[i] = newline, ....
|
f
est réel, et si
i
est entier, l'expression f +i
est un réel qui s'obtient par
la conversion implicite de i
vers un float
. Certaines
conversions sont interdites, comme par exemple indicer un tableau par un
nombre réel. En général, on essaie de faire la plus petite conversion
permettant de faire l'opération (cf. figure
A.1). Ainsi un caractère n'est qu'un petit entier.
Ceci permet de faire facilement certaines fonctions comme la fonction qui
convertit une chaîne de caractères ASCII en un entier (atoi
est un
raccourci pour Ascii To Integer)
static int atoi (String s) { int n = 0; for (int i = 0; i < s.length(); ++i) n = 10 * n + (s.charAt(i) - '0'); return n; } |
s.charAt(i) - '0'
permet de
calculer l'entier qui représente la différence entre le code de
s.charAt(i)
et celui de '0'
. N'oublions pas que cette fonction
est plus simplement calculée par Integer.parseInt(s)
.
Les conversions implicites suivent la figure A.1. Pour toute opération, on convertit toujours au le plus petit commun majorant des types des opérandes. Des conversions explicites sont aussi possibles, et recommandées dans le doute. On peut les faire par l'opération de coercion (cast) suivante
(type-name) expression |
=
d'affectation est un
opérateur comme les autres dans les expressions. Il subit donc les mêmes
lois de conversion. Toutefois, il se distingue des autres opérations par le
type du résultat. Pour un opérateur ordinaire, le type du résultat est le
type commun obtenu par conversion des deux opérandes. Pour une affectation,
le type du résultat est le type de l'expression à gauche de
l'affectation. Il faut donc faire une conversion explicite sur l'expression
de droite pour que le résultat soit cohérent avec le type de l'expression de
gauche. Enfin, dans les appels de fonctions, il y a aussi une opération
similaire à une affectation pour passer les arguments, et donc des
conversions des arguments sont possibles.
|
x = y = z = 1; |
x = 1; y = 1; z = 1; |
|
n
vaille 5, alors le programme
suivant
x = ++n; y = n++; z = --n; t = n--; |
n
à 6, met 6 dans x
, met 6 dans
y
, fait passer n
à 7, puis retranche 1 à n
pour
lui donner la valeur 6 à nouveau, met cette valeur 6 dans z
et
dans t
, et fait passer n
à 5. Plus simplement, on peut
écrire simplement
++i; j++; |
i = i + 1; j = j + 1; |
if (c != ' ') c = s.charAt.(i++); |
if (c != ' ') { c = s.charAt.(i); ++i; } |
On ne doit pas faire trop d'effets de bord dans une même expression
|
&
(et logique), |
(ou
logique), ^
(ou exclusif), <<
(décalage vers la
gauche), >>
(décalage vers la droite), ~
(complément à
un). Ainsi
x = x & 0xff; y = y | 0x40; |
x
les 8 derniers bits de x
et
positionne le 6ème bit à partir de la droite
dans y
. Il faut bien distinguer les opérations logiques
&&
et ||
à résultat booléens 0 ou 1 des opérations
&
et |
sur les bits qui donnent toute valeur
entière. Par exemple, si x
vaut 1 et y
vaut 2,
x &y
vaut 0 et x &&y
vaut 1.<<
et >>
décalent leur opérande de
gauche de la valeur indiquée par l'opérande de droite. Ainsi
3 <<2
vaut 12, et 7 >>2
vaut 1. Les décalages à gauche
introduisent toujours des zéros sur les bits de droite. Pour les bits
de gauche dans le cas des décalages à droite, c'est dépendant de
la machine; mais si l'expression décalée est unsigned
, ce
sont toujours des zéros.x = x & ~0x7f; |
x
,
indépendamment du nombre de bits pour représenter un entier. Une
notation, supposant des entiers sur 32 bits et donc dépendante de la
machine, seraitx = x & 0xffff8000; |
|
+
,
-
, *
, /
, \prog
|, ^
, ou |
,e1 Å= e2 |
e1 = e1 Å e2 |
|
if (a > b) z = a; else z = b; |
e1 ? e2 : e3 |
a
et b
peut s'écrirez = (a > b) ? a : b; |
|
Opérateurs | Associativité | ||
() [ ] ->. |
gauche à droite | ||
! ~ ++ -- + - = * & ( type)sizeof |
droite à gauche | ||
* /% |
gauche à droite | ||
+- |
gauche à droite | ||
<<>> |
gauche à droite | ||
< <= >>= |
gauche à droite | ||
==!= |
gauche à droite | ||
& |
gauche à droite | ||
^ |
gauche à droite | ||
| |
gauche à droite | ||
&& |
gauche à droite | ||
|| |
gauche à droite | ||
?: |
droite à gauche | ||
= += -= /= %= &= ^= |= <<=>>= |
droite à gauche | ||
, |
gauche à droite |
if ((x & MASK) == 0) ... |
|
x = 3; ++i; System.out.print(...); |
| \prog|
permettent de regrouper des instructions en séquence. Ce qui permet
de mettre plusieurs instructions dans les alternatives d'un if
par exemple.if
sont de la
forme
if (E) S1 |
if (E) S1 else S2 |
if (x < 10) c = '0' + x; else c = 'a' + x - 10; |
if
emboîtés. Le else
se rapportant toujours au if
le plus
proche. Une série de if
peut être remplacée par une
instruction de sélection par cas, c'est l'instruction switch
.
Elle a la syntaxe suivante
switch (E) { case c1: instructions case c2: instructions ... case cn: instructions default: instructions } |
break
. Sinon, le reste de l'instruction est fait en séquence.
Cela permet de regrouper plusieurs alternatives, mais peut être
particulièrement dangereux. Par exemple, le programme suivant
switch (c) { case '\t': case ' ': ++ nEspaces; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': ++ nChiffres; break; default: ++ nAutres; break; } |
break
permet aussi de sortir des boucles. Il
faudra donc bien faire attention à ne pas oublier le break
à
la fin de chaque cas, et à ce que break
ne soit pas
intercepté par une autre instruction.for
, while
,
et do...while
. L'instruction while
permet d'itérer tant qu'une
expression booléenne est vraie, on itère l'instruction S tant que la
condition E est vraie par
while (E) S |
do S while (E); |
for
. Sa syntaxe est
for (E1; E2; E3) S |
E1; while (E2) { S; E3; } |
for (i = 0; i < 100; ++i) a[i] = 0; |
for (int i = h(x); i != -1; i = col[i]) if (x.equals(nom[i])) return tel[i]; |
break
permet de sortir d'une
instruction switch
, mais aussi de toute instruction
d'itération. De même, l'instruction continue
permet de passer
brusquement à l'itération suivante. Ainsi
for (i = 0; i < n; ++i) { if (a[i] < 0) continue; ... } |
break
et continue
peuvent préciser l'étiquette de l'itération
qu'elles référencent. Ainsi, dans l'exemple suivant, on déclare une
étiquette devant l'instruction while
, et on sort des deux itérations
comme suit:
boucle: while (E) { for (i = 0; i < n; ++i) { if (a[i] < 0) break boucle; ... } } |
goto
. Il y a toutefois des
exceptions que nous verrons plus tard.
|
main
qui prend un
tableau de chaînes de caractères comme argument. Pour déclarer une
fonction, on déclare d'abord sa signature, c'est à dire le type de son
résultat et des arguments, puis son corps, c'est à dire la suite
d'instructions qui la réalisent entre accolades. Ainsi dans
static int suivant (int x) { if (x % 2 == 1) return 3 * x + 1; else return x / 2; } |
int
avant
suivant
) et l'unique argument x est aussi entier.
L'instructionreturn e; |
void
et l'argument est entier.
static void test (int x) { while (x != 1) x = suivant (x); } static void testConjecture (int n) { for (int x=1; x <= n; ++x) { test (x); System.out.println (x); } } |
suivant
précédente, on a
changé la valeur du paramètre x dans le corps de la fonction. Mais ceci
n'est vrai qu'à l'intérieur du corps de la fonction. En Java, seule la
valeur du paramètre compte, on ne modifiera donc pas ainsi une variable
extérieure à la fonction, passée en paramètre.
Les paramètres des fonctions sont passés par valeur
|
class Date { int j; /* Jour */ int m; /* Mois */ int a; /* Année */ }; |
static final int Jan=1, Fev=2, Mar=3, Avr=4, Mai=5, Juin=6, Juil=7, Aou=8, Sep=9, Oct=10, Nov=11, Dec=12; Date bastille = new Date(), berlin = new Date(); bastille.j = 14; bastille.m = Juil; bastille.a = 1789; berlin.j = 10; berlin.m = Nov; berlin.a = 1989; |
bastille
et
berlin
, et pour accéder à leurs champs on utilise la notation bien
connue suffixe avec un point. Le champ jour de la date de la prise de la
Bastille s'obtient donc par bastille.j
. Rien de neuf, c'est la
notation utilisée en Pascal, en C, ou en Caml. Pour créer un objet, on
utilise le mot-clé new
suivi du nom de la classe et de parenthèses.
(Pour les experts, les objets sont représentés par un pointeur et leur
contenu se trouve dans le tas). Un objet non initialisé vaut null
.
class Date { int j; /* Jour */ int m; /* Mois */ int a; /* Année */ Date (int jour, int mois, int annee) { this.j = jour; this.m = mois; this.a = annee; }; |
static Date berlin = new Date(10, Nov, 1989), bastille = new Date(14, Juil, 1789); |
new
. Dans le cas où il n'y a pas de
constructeur explicite, le constructeur par défaut (sans arguments)
réserve juste l'espace mémoire nécessaire pour l'objet
construit. Remarquons que dans le constructeur, on a utilisé le
mot-clé this
qui désigne l'objet en cours de création pour bien
comprendre que j
, m
et a
sont des champs de
l'objet construit. Le constructeur se finit donc implicitement par
returnthis
. Mais, ce mot-clé n'était pas vraiment utile. On
aurait pu simplement écrire
class Date { int j; /* Jour */ int m; /* Mois */ int a; /* Année */ Date (int jour, int mois, int annee) { j = jour; m = mois; a = annee; }; |
class Date { int j; /* Jour */ int m; /* Mois */ int a; /* Année */ static final int Jan=1, Fev=2, Mar=3, Avr=4, Mai=5, Juin=6, Juil=7, Aou=8, Sep=9, Oct=10, Nov=11, Dec=12; static Date tempsZeroUnix = new Date (1, Jan, 1970); static int nbInstances = 0; Date (int jour, int mois, int annee) { j = jour; m = mois; a = annee; ++ nbInstances; }; |
static
, les
méthodes dynamiques n'ont pas de préfixe. La syntaxe est celle d'une
fonction usuelle. Prenons le cas de l'impression de notre classe Date.
class Date { ... static void imprimer (Date d) { System.out.print ("d = " + d.j + ", " + "m = " + d.m + ", " + "a = " + d.a); } } |
Date.imprimer (berlin); Date.imprimer (bastille); |
class Date { ... static boolean equals (Date d1, Date d2) { return d1.j == d2.j && d1.m == d2.m && d1.a == d2.a; } } |
this
qui est l'objet dont elles sont la
méthode. (Pour accéder aux champs de l'objet, this
est facultatif).
Ce changement, qui rend plus proches les fonctions et les données, peut
paraître mineur, mais il est à la base de la programmation objet, car il se
combinera à la notion de sous-classe. Prenons l'exemple des deux méthodes
statiques écrites précédemment. Nous pouvons les réécrire non statiquement
comme suit:
class Date { ... void print () { System.out.print ("d = " + this.j + ", " + "m = " + this.m + ", " + "a = " + this.a); } boolean equals (Date d) { return this.j == d.j && this.m == d.m && this.a == d.a; } } |
this
non nécessaire ici:
class Date { ... void print () { System.out.print ("d = " + j + ", " + "m = " + m + ", " + "a = " + a); } boolean equals (Date d) { return j == d.j && m == d.m && a == d.a; } } |
if (!Date.equals(berlin, bastille)) Date.imprimer (berlin); |
if (!berlin.equals(bastille)) berlin.print (); |
println
est une méthode associé au flux de
sortie out
, qui lui-même est une donnée statique de la classe
System
. De même pour les chaînes de caractères: la classe
String
définit les méthodes length
, charAt
pour obtenir
la longueur de l'objet chaîne s
ou lire le caractère à la position
i dans s
comme suit:
String s = "Oh, la belle chaîne"; if (s.length() > 20) System.out.println (s.charAt(i)); |
toString
peut être prise en compte par le système d'écriture
standard. Ainsi, dans le cas des dates, si on avait déclaré,
class Date { ... public String toString () { return ("d = " + j + ", " + "m = " + m + ", " + "a = " + a); } } |
if (!berlin.equals(bastille)) System.out.println (berlin); |
static
pour
initialiser divers champs à chaque création d'un objet ou au chargement de
la classe.clone
).
Deuxièmement, il n'y a pas d'instruction pour détruire des objets. Ce n'est pas grave, car le garbage collector (GC) récupère automatiquement l'espace mémoire des objets non utilisés. Cela est fait régulièrement, notamment quand il n'y a plus de place en mémoire. C'est un service de récupération des ordures, comme dans la vie courante. Il n'y a donc pas à se soucier de la dé-allocation des objets. Troisièmement, il est possible de surcharger les méthodes en faisant varier le type de leurs arguments ou leur nombre. Nous avons déjà vu le cas de
Les objets ne sont jamais copiés implicitement
println
qui
prenait zéro arguments ou un argument de type quelconque (qui était en
fait transformé en chaîne de caractères avec la méthode
toString
). On peut déclarer très simplement de telles méthodes
surchargées, par exemple dans le cas des constructeurs:
class Date { int j; /* Jour */ int m; /* Mois */ int a; /* Année */ Date (int jour, int mois, int annee) { j = jour; m = mois; a = annee; Date (long n) { // Un savant calcul du jour, mois et année à partir du nombre // de millisecondes depuis l'origine des temps informatiques, ie // le 1 janvier 1970. } Date () { // idem avec le temps courant \progt{System.currentTimeMillis} } }; |
La surcharge est résolue statiquement à la compilation.
|
class Point { int x, y; Point (int x0, int y0) { x = x0; y = y0; } public String toString () { return "(" + x + ", " + y +")"; } void move (int dx, int dy) { x = x + dx; y = y + dy; } } |
move
pour bouger un
point, cette dernière méthode étant une procédure qui renvoie donc le type
vide. On peut utiliser des objets de cette classe en écrivant des
instructions de la forme
Point p = new Point(1, 2); System.out.println (p); p.move (-1, -1); System.out.println (p); |
couleur
, en la déclarant comme une extension, ou
encore une sous-classe, de la classe des points, comme suit:
class PointAvecCouleur extends Point { int couleur; PointAvecCouleur (int x0, int y0, int c) { super (x0, y0); couleur = c; } public String toString () { return "(" + x + ", " + y + ", " + couleur + ")"; } } |
super.couleur
grâce au mot-clé
super
. Dans la nouvelle classe, nous avons un constructeur qui prend
un argument supplémentaire pour la couleur. Ce constructeur doit toujours
commencer par un appel à un constructeur de la super-classe (la classe des
points). Si on ne met pas d'appel explicite à un constructeur de cette
classe, l'instruction super()
est faite implicitement, et ce
constructeur doit alors exister dans la super classe. Enfin, la méthode
toString
est redéfinie pour prendre en compte le nouveau champ pour
la couleur. On utilise les points colorés comme suit
PointAvecCouleur q = new PointAvecCouleur (3, 4, 0xfff); System.out.println (q); q.move(10,-1); System.out.println (q); |
move
de la classe des points, mais la méthode
toString
a été redéfinie. On n'a eu donc qu'à programmer l'incrément
entre les points normaux et les points colorés. C'est le principe de base de
la programmation objet: le contrôle des programmes est dirigé par les
données et leurs modifications. Dans la programmation classique, on doit
changer le corps de beaucoup de fonctions ou de procédures si on change les
déclarations des données, car la description d'un programme est donné par sa
fonctionnalité.final
.
On ne peut non plus donner des modificateurs d'accès plus restrictifs, une
méthode publique devant rester publique. Une classe hérite d'une seule
classe (héritage simple). Des langages comme Smalltalk ou C++ autorisent
l'héritage multiple à partir de plusieurs classes, mais les méthodes sont
alors un peu plus délicates à implémenter. L'héritage est bien sûr
transitif. D'ailleurs toutes les classes Java héritent d'une unique classe
Object
.Enfin, il y a une façon de convertir un objet en objet d'une super-classe ou d'une sous-classe avec la notation des conversions explicites (déjà vue pour le cas des valeurs numériques). Par exemple
La résolution des méthodes dynamiques est faite à l'exécution.
Point p = new Point (10, 10); PointAvecCouleur q = new PointAvecCouleur (20, 20, ROUGE); Point p1 = q; PointAvecCouleur q1 = (PointAvecCouleur) p; PointAvecCouleur q2 = (PointAvecCouleur) p1; |
ClassCastException
. Dans
l'exemple précédent seule l'avant-dernière ligne lèvera cette exception.
|
length
indique leur longueur. L'accès aux éléments du tableau a s'écrit avec des
crochets, a[i-1]
représente i-ème élément. Les tableaux n'ont
qu'une seule dimension, un tableau à deux dimensions est considéré comme un
tableau dont tous les éléments sont des tableaux à une dimension, etc. Si on
accède en dehors du tableau, une exception est levée. La création d'un
tableau se fait avec le mot-clé new
comme pour les objets, mais il
existe une facilité syntaxique pour créer des tableaux à plusieurs
dimensions. Voici par exemple le calcul de la table de vérité de l'union de
deux opérateurs booléens:
static boolean[ ][ ] union (boolean[ ][ ] a, boolean[ ][ ] b) { boolean[ ][ ] c = new boolean [2][2]; for (int i = 0; i < 2; ++i) for (int j = 0; j < 2; ++j) c[i][j] = a[i][j] || b[i][j]; return c; } |
boolean[ ][ ] intersection = {{true, false},{false, false}}; boolean[ ][ ] ouExclusif = {{false, true},{true, false}}; |
|
Exception
. Il existe aussi une classe Error
moins utilisée
pour les erreurs système. Toutes les deux sont des sous-classes de la classe
Throwable
, dont tous les objets peuvent être appliqués à l'opérateur
throw
, comme suit:
throw e; |
Exception
:
throw new Exception(); throw new Exception ("Accès interdit dans un tableau"); |
IndexOutOfBoundsExeption
. On récupère une exception par l'instruction
try
...catch
. Par exemple
try { // un programme compliqué } catch ( IOException e) { // essayer de réparer cette erreur d'entrée/sortie } catch ( Exception e) { // essayer de réparer cette erreur plus générale } |
try
, que l'on passe ou non par une exception, que le contrôle sorte
ou non de l'instruction par une rupture de séquence comme un return
,
break
, etc, on écrit
try { // un programme compliqué } catch ( IOException e) { // essayer de réparer cette erreur d'entrée/sortie } catch ( Exception e) { // essayer de réparer cette erreur plus générale } finally { // un peu de nettoyage } |
throws
dans la signature des fonctions
qui les lèvent. Ce n'est pas la peine pour les exceptions non
vérifiées qui se reconnaissent en appartenant à une sous-classe de la
classe RuntimeException
. Ainsi
static int lire () throws IOException, ParseException { int n; BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); System.out.print ("Taille du carré magique, svp?:: "); n = Integer.parseInt (in.readLine()); if ((n <= 0) || (n > N) || (n % 2 == 0)) erreur ("Taille impossible."); return n; } |
|
System.in
et
System.out
sont deux champs statiques (donc uniques) de la classe
système, qui sont respectivement des InputStream
et
PrintStream
. Dans cette dernière classe, il y a notamment les
méthodes: flush
qui vide les sorties non encore effectuées,
print
et println
qui impriment sur le terminal leur argument
avec éventuellement un retour à la ligne. Pour l'impression, l'argument de
ces fonctions est quelconque, (éventuellement vide pour la deuxième). Elles
sont surchargées sur pratiquement tous les types, et transforment leur
argument en chaîne de caractères. Ainsi:
|
donnent |
|
InputStream
et PrintStream
lisent ou
impriment des octets (byte
). Il vaut mieux faire des opérations avec
des caractères Unicode (sur 16 bits, qui comprennent tous les caractères
internationaux). Pour cela, au lieu de fonctionner avec les flux d'octets
(Stream tout court), on utilise les classes des Reader ou des
Writer, comme InputStreamReader
et OutputStreamWriter
,
qui manipulent des flux de caractères. Dans ces classes, il existe de
nombreuses méthodes ou fonctions. Ici, nous considérons les entrées-sorties
avec un tampon (buffer en anglais), qui sont plus efficaces, car elles
regroupent les opérations d'entrées-sorties. C'est pourquoi, on écrit
souvent la ligne cryptique:
struct Noeud { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); |
System.in
(désignant la fenêtre d'entrée de texte par défaut), un flux de caractères,
puis un flux de caractères avec tampon (plus efficace). Dans cette dernière
classe, on lit les caractères par read()
ou readLine()
. Par
convention, read()
retourne -1 quand la fin de l'entrée est détectée
(comme en C). C'est pourquoi le type de son résultat est un entier (et non
un caractère), puisque -1 ne peut pas être du type caractère. Pour lire
l'entrée terminal et l'imprimer immédiatement, on fait donc:
import java.io.*; static void copie () throws Exception { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); int c; while ((c = in.read()) != -1) System.out.println(c); System.out.flush(); } |
while
. On fait l'affectation
c = in.read()
qui retourne comme résultat la valeur de la partie droite (le
caractère lu) et on teste si cette valeur vaut -1.File
. Ainsi le programme précédent se réécrit pour copier un fichier
source de nom s
dans un autre destination de nom d
.
import java.io.*; static void copieDeFichiers (String s, String d) throws Exception { File src = new File (s); if ( !src.exists() || !src.canRead()) erreur ("Lecture impossible de " + s); BufferedReader in = new BufferedReader(new FileReader(src)); File dest = new File (d); if ( !dest.canWrite()) erreur ("Ecriture impossible de " + d); BufferedWriter out = new BufferedWriter(new FileWriter(dest)); maCopie (in, out); in.close(); out.close(); } static void maCopie (BufferedReader in, BufferedWriter out) throws IOException { int c; while ((c = in.read()) != -1) out.write(c); out.flush(); } |
print
en write
, car nous n'avons pas voulu
utiliser la classe Printer, ce qui était faisable. Remarquons que les
entrées sorties se sont simplement faites avec les fichiers en suivant un
schéma quasi identique à celui utilisé pour le terminal. La seule différence
vient de l'association entre le nom de fichier et les flux de caractères
tamponnés. D'abord le nom de fichier est transformé en objet File
sur
lequel plusieurs opérations sont possibles, comme vérifier l'existence ou
les droits d'accès en lecture ou en écriture. Puis on construit un objet de
flux de caractères dans les classes FileReader
et FileWriter
,
et enfin des objets de flux de caractères avec tampon. La procédure de copie
est elle identique à celle vue précédemment.System.in
(de la fenêtre de
texte), la sortie standard System.out
(dans la fenêtre de texte), et
la sortie standard des erreurs System.err
(qui n'a vraiment de sens
que dans le système Unix) sont comme des fichiers particuliers. Les
opérations de lecture ou d'écriture étant les mêmes, seule la construction
du flux de caractères tamponné varie.mark
, skip
et reset
pour se
positionner à une position précise dans un fichier.
|
h
(horizontal) et
v
(vertical). Il y a une notion de point courant et de crayon
avec une taille et une couleur courantes. On peut déplacer le crayon,
en le levant ou en dessinant des vecteurs par les fonctions suivantes
r
a un type prédéfini Rect
. Ce type est une classe
qui a le format suivant
public class Rect { short left, top, right, bottom; } |
class Point { short h, v; Point(int h, int v) { h = (short)h; v = (short)v; } } class MacLib { static void setPt(Point p, int h, int v) {..} static void addPt(Point src, Point dst) {...} static void subPt(Point src, Point dst) {...} static boolean equalPt(Point p1, Point p2) {...} ... } |
static void setRect(Rect r, int left, int top, int right, int bottom) static void unionRect(Rect src1, Rect src2, Rect dst) static void frameRect(Rect r) static void paintRect(Rect r) static void eraseRect(Rect r) static void invertRect(Rect r) static void frameOval(Rect r) static void paintOval(Rect r) static void eraseOval(Rect r) static void invertOval(Rect r) static void frameArc(Rect r, int startAngle, int arcAngle) static void paintArc(Rect r, int startAngle, int arcAngle) static void eraseArc(Rect r, int startAngle, int arcAngle) static void invertArc(Rect r, int startAngle, int arcAngle) static boolean button() static void getMouse(Point p) |
poly
dans le fichier
/usr/local/lib/MacLib-java/MacLib.java |
CLASSPATH
). Le programme suivant est un
programme qui fait rebondir une balle dans un rectangle, première étape vers
un jeu de pong.
class Pong extends MacLib { static final int C = 5, // Le rayon de la balle X0 = 5, X1 = 250, Y0 = 5, Y1 = 180; static void getXY (Point p) { int N = 2; Rect r = new Rect(); int x, y; while (!button()) // On attend le bouton enfoncé ; while (button()) // On attend le bouton relâché ; getMouse(p); // On note les coordonnées du pointeur x = p.h; y = p.v; setRect(r, x - N, y - N, x + N, y + N); paintOval(r); // On affiche le point pour signifier la lecture } public static void main (String[ ] args) { int x, y, dx, dy; Rect r = new Rect(); Rect s = new Rect(); Point p = new Point(); int i; initQuickDraw(); // Initialisation du graphique setRect(s, 50, 50, X1 + 100, Y1 + 100); setDrawingRect(s); showDrawing(); setRect(s, X0, Y0, X1, Y1); frameRect(s); // Le rectangle de jeu getXY(p); // On note les coordonnées du pointeur x = p.h; y = p.v; dx = 1; // La vitesse initiale dy = 1; // de la balle for (;;) { setRect(r, x - C, y - C, x + C, y + C); paintOval(r); // On dessine la balle en $x,y$ x = x + dx; if (x - C <= X0 + 1 || x + C >= X1 - 1) dx = -dx; y = y + dy; if (y - C <= Y0 + 1 || y + C >= Y1 - 1) dy = -dy; for (i = 0; i < 2500; ++i) ; // On temporise invertOval(r); // On efface la balle } } } |
|
Goal: CompilationUnit
|
Literal: IntegerLiteral FloatingPointLiteral BooleanLiteral CharacterLiteral StringLiteral NullLiteral
|
Type: PrimitiveType ReferenceType PrimitiveType: NumericType boolean NumericType: IntegralType FloatingPointType IntegralType: one of byte short int long char FloatingPointType: one of float double ReferenceType: ClassOrInterfaceType ArrayType ClassOrInterfaceType: Name ClassType: ClassOrInterfaceType InterfaceType: ClassOrInterfaceType ArrayType: PrimitiveType [ ] Name [ ] ArrayType [ ]
|
Name: SimpleName QualifiedName SimpleName: Identifier QualifiedName: Name . Identifier
|
CompilationUnit: PackageDeclarationopt ImportDeclarationsopt TypeDeclarationsopt ImportDeclarations: ImportDeclaration ImportDeclarations ImportDeclaration TypeDeclarations: TypeDeclaration TypeDeclarations TypeDeclaration PackageDeclaration: package Name ; ImportDeclaration: SingleTypeImportDeclaration TypeImportOnDemandDeclaration SingleTypeImportDeclaration: import Name ; TypeImportOnDemandDeclaration: import Name . * ; TypeDeclaration: ClassDeclaration InterfaceDeclaration ; Modifiers: Modifier Modifiers Modifier Modifier: one of public protected private static abstract final native synchronized transient volatile
|
|
ClassDeclaration: Modifiersopt class Identifier Superopt Interfacesopt ClassBody Super: extends ClassType Interfaces: implements InterfaceTypeList InterfaceTypeList: InterfaceType InterfaceTypeList , InterfaceType ClassBody: { ClassBodyDeclarationsopt } ClassBodyDeclarations: ClassBodyDeclaration ClassBodyDeclarations ClassBodyDeclaration ClassBodyDeclaration: ClassMemberDeclaration StaticInitializer ConstructorDeclaration ClassMemberDeclaration: FieldDeclaration MethodDeclaration
|
FieldDeclaration: Modifiersopt Type VariableDeclarators ; VariableDeclarators: VariableDeclarator VariableDeclarators , VariableDeclarator VariableDeclarator: VariableDeclaratorId VariableDeclaratorId = VariableInitializer VariableDeclaratorId: Identifier VariableDeclaratorId [ ] VariableInitializer: Expression ArrayInitializer
|
MethodDeclaration: MethodHeader MethodBody MethodHeader: Modifiersopt Type MethodDeclarator Throwsopt Modifiersopt void MethodDeclarator Throwsopt MethodDeclarator: Identifier ( FormalParameterListopt ) MethodDeclarator [ ] FormalParameterList: FormalParameter FormalParameterList , FormalParameter FormalParameter: Type VariableDeclaratorId Throws: throws ClassTypeList ClassTypeList: ClassType ClassTypeList , ClassType MethodBody: Block ;
|
StaticInitializer: static Block
|
ConstructorDeclaration: Modifiersopt ConstructorDeclarator Throwsopt ConstructorBody ConstructorDeclarator: SimpleName ( FormalParameterListopt ) ConstructorBody: { ExplicitConstructorInvocationopt BlockStatementsopt } ExplicitConstructorInvocation: this ( ArgumentListopt ) ; super ( ArgumentListopt ) ;
|
InterfaceDeclaration: Modifiersopt interface Identifier ExtendsInterfacesopt InterfaceBody ExtendsInterfaces: extends InterfaceType ExtendsInterfaces , InterfaceType InterfaceBody: { InterfaceMemberDeclarationsopt } InterfaceMemberDeclarations: InterfaceMemberDeclaration InterfaceMemberDeclarations InterfaceMemberDeclaration InterfaceMemberDeclaration: ConstantDeclaration AbstractMethodDeclaration ConstantDeclaration: FieldDeclaration AbstractMethodDeclaration: MethodHeader ;
|
ArrayInitializer: { VariableInitializersopt ,opt } VariableInitializers: VariableInitializer VariableInitializers , VariableInitializer
|
Block: { BlockStatementsopt } BlockStatements: BlockStatement BlockStatements BlockStatement BlockStatement: LocalVariableDeclarationStatement Statement LocalVariableDeclarationStatement: LocalVariableDeclaration ; LocalVariableDeclaration: Type VariableDeclarators Statement: StatementWithoutTrailingSubstatement LabeledStatement BlockStatementsBlockStatementsIfThenStatement IfThenElseStatement WhileStatement ForStatement StatementNoShortIf: StatementWithoutTrailingSubstatement LabeledStatementNoShortIf IfThenElseStatementNoShortIf WhileStatementNoShortIf ForStatementNoShortIf StatementWithoutTrailingSubstatement: Block EmptyStatement ExpressionStatement SwitchStatement DoStatement BreakStatement ContinueStatement ReturnStatement SynchronizedStatement ThrowStatement TryStatement EmptyStatement: ; LabeledStatement: Identifier : Statement LabeledStatementNoShortIf: Identifier : StatementNoShortIf ExpressionStatement: StatementExpression ; StatementExpression: Assignment PreIncrementExpression PreDecrementExpression PostIncrementExpression PostDecrementExpression MethodInvocation ClassInstanceCreationExpression IfThenStatement: if ( Expression ) Statement IfThenElseStatement: if ( Expression ) StatementNoShortIf else Statement IfThenElseStatementNoShortIf: if ( Expression ) StatementNoShortIf else StatementNoShortIf SwitchStatement: switch ( Expression ) SwitchBlock SwitchBlock: { SwitchBlockStatementGroupsopt SwitchLabelsopt } SwitchBlockStatementGroups: SwitchBlockStatementGroup SwitchBlockStatementGroups SwitchBlockStatementGroup SwitchBlockStatementGroup: SwitchLabels BlockStatements SwitchLabels: SwitchLabel SwitchLabels SwitchLabel SwitchLabel: case ConstantExpression : default : WhileStatement: while ( Expression ) Statement WhileStatementNoShortIf: while ( Expression ) StatementNoShortIf DoStatement: do Statement while ( Expression ) ; ForStatement: for ( ForInitopt ; Expressionopt ; ForUpdateopt ) Statement ForStatementNoShortIf: for ( ForInitopt ; Expressionopt ; ForUpdateopt ) StatementNoShortIf ForInit: StatementExpressionList LocalVariableDeclaration ForUpdate: StatementExpressionList StatementExpressionList: StatementExpression StatementExpressionList , StatementExpression BreakStatement: break Identifieropt ; ContinueStatement: continue Identifieropt ; ReturnStatement: return Expressionopt ; ThrowStatement: throw Expression ; SynchronizedStatement: synchronized ( Expression ) Block TryStatement: try Block Catches try Block Catchesopt Finally Catches: CatchClause Catches CatchClause CatchClause: catch ( FormalParameter ) Block Finally: finally Block
|
Primary: PrimaryNoNewArray ArrayCreationExpression PrimaryNoNewArray: Literal this ( Expression ) ClassInstanceCreationExpression FieldAccess MethodInvocation ArrayAccess ClassInstanceCreationExpression: new ClassType ( ArgumentListopt ) ArgumentList: Expression ArgumentList , Expression ArrayCreationExpression: new PrimitiveType DimExprs Dimsopt new ClassOrInterfaceType DimExprs Dimsopt DimExprs: DimExpr DimExprs DimExpr DimExpr: [ Expression ] Dims: [ ] Dims [ ] FieldAccess: Primary . Identifier super . Identifier MethodInvocation: Name ( ArgumentListopt ) Primary . Identifier ( ArgumentListopt ) super . Identifier ( ArgumentListopt ) ArrayAccess: Name [ Expression ] PrimaryNoNewArray [ Expression ] PostfixExpression: Primary Name PostIncrementExpression PostDecrementExpression PostIncrementExpression: PostfixExpression ++ PostDecrementExpression: PostfixExpression -- UnaryExpression: PreIncrementExpression PreDecrementExpression + UnaryExpression - UnaryExpression UnaryExpressionNotPlusMinus PreIncrementExpression: ++ UnaryExpression PreDecrementExpression: -- UnaryExpression UnaryExpressionNotPlusMinus: PostfixExpression ~ UnaryExpression ! UnaryExpression CastExpression CastExpression: ( PrimitiveType Dimsopt ) UnaryExpression ( Expression ) UnaryExpressionNotPlusMinus ( Name Dims ) UnaryExpressionNotPlusMinus MultiplicativeExpression: UnaryExpression MultiplicativeExpression * UnaryExpression MultiplicativeExpression / UnaryExpression MultiplicativeExpression % UnaryExpression AdditiveExpression: MultiplicativeExpression AdditiveExpression + MultiplicativeExpression AdditiveExpression - MultiplicativeExpression ShiftExpression: AdditiveExpression ShiftExpression << AdditiveExpression ShiftExpression >> AdditiveExpression ShiftExpression >>> AdditiveExpression RelationalExpression: ShiftExpression RelationalExpression < ShiftExpression RelationalExpression > ShiftExpression RelationalExpression <= ShiftExpression RelationalExpression >= ShiftExpression RelationalExpression instanceof ReferenceType EqualityExpression: RelationalExpression EqualityExpression == RelationalExpression EqualityExpression != RelationalExpression AndExpression: EqualityExpression AndExpression & EqualityExpression ExclusiveOrExpression: AndExpression ExclusiveOrExpression ^ AndExpression InclusiveOrExpression: ExclusiveOrExpression InclusiveOrExpression | ExclusiveOrExpression ConditionalAndExpression: InclusiveOrExpression ConditionalAndExpression && InclusiveOrExpression ConditionalOrExpression: ConditionalAndExpression ConditionalOrExpression || ConditionalAndExpression ConditionalExpression: ConditionalOrExpression ConditionalOrExpression ? Expression : ConditionalExpression AssignmentExpression: ConditionalExpression Assignment Assignment: LeftHandSide AssignmentOperator AssignmentExpression LeftHandSide: Name FieldAccess ArrayAccess AssignmentOperator: one of = *= /= %= += -= <<= >>= >>>= &= ^= |= Expression: AssignmentExpression ConstantExpression: Expression
|