Java avancé

Transcription

Java avancé
Java avancé
Concepts avancés du Java
Contenu
1. Contrôle de l'exécution du programme............................................................................1
2. Initialisation et nettoyage....................................................................................................3
3. Cacher l'implémentation.....................................................................................................5
4. Réutiliser les classes.............................................................................................................6
5. Interfaces et classes internes...............................................................................................7
6. Gestion des erreurs avec les exceptions..........................................................................13
7. Interroger les types (Run-Time Type Identification)....................................................15
8. Collections d'objets............................................................................................................16
9. Sérialisation.........................................................................................................................22
10. Concurrence......................................................................................................................23
11. Le clonage d'objet.............................................................................................................28
12. Swing..................................................................................................................................29
1. Contrôle de l'exécution du programme
1.1 Évaluation paresseuse
Les expressions booléennes sont évaluées jusqu'à ce que le succès ou l'échec de
l'expression totale puisse être déterminé sans ambiguïté. En résultat, la fin d'une expression
logique peut ne pas être évaluée. Dans l'exemple suivant, si test1() est faux, test2() et test3()
ne seront pas exécutés.
if (test1() && test2() && test3()) {
System.out.println(“expression is true”);
} else {
System.out.println(“expression is false”);
}
1.2 Les opérateurs de décalage de bits
Aux opérateurs classiques '>>' et '<<', Java ajoute l'opérateur de décalage à droite non
signé '>>>', qui utilise l'extension avec des zéros sans se soucier du signe ; des zéros sont
insérés aux bits de poids forts. Le décalage signé à droite '>>' utilise l'extension de signe : si
la valeur est positive, des zéros sont insérés aux bits de poids forts, si elle est négative, des
uns sont insérés pour les bits de poids forts.
Si on décale un char, un byte ou un short, il sera promu en int avant de faire le décalage.
Tous les autres opérateurs de décalage ont le même effet qu'en C.
1.2.1 Exemple
int i=-1
École Nationale Supérieure d'Ingénieurs
& Groupe de Laboratoires, C.N.R.S.
6, Boulevard Maréchal Juin, F-14050 CAEN cedex
http://www.ensicaen.fr
2 ■ Java avancé
i >>>= 31;
System.out.println(i);
result : 1
long l = -1;
l >>>= 63;
System.out.println(l);
result : 1
short s = -1;
s >>>= 10;
System.out.println(s);
// Error truncated
result : -1
short s = -1;
s >>>= 31;
System.out.println(s);
result : 1
byte b = -1;
b >>>= 10;
System.out.println(b);
// Error truncated
result : -1
byte b = -1;
System.out.println(b>>>31);
// Correct
result : 1
ENSICAEN - Spécialité Informatique
1.3 Le sulfureux « goto »
Java n'a pas de goto, mais le mot clé est réservé.
1.4 L'opérateur « sizeof »
Java n'a pas d'opérateur sizeof(). Java détermine la taille de chaque type primitif
indépendamment du système d'exploitation. Tous les types numériques sont signés ; il n'y a
pas de type non signé. Chaque type primitif possède une classe associée (un Wrapper).
Type primitif
Size
Minimum
Maximum
Classe associée
boolean
-
-
-
Boolean
char
16-bit
Unicode 0
Unicode 216- 1
Character
byte
8-bit
-128
127
Byte
short
16-bit
-215
+215-1
Short
int
32-bit
-231
+231-1
Integer
long
64-bit
-263
+263-1
Long
float
32-bit
IEEE754
IEEE754
Float
double
64-bit
IEEE754
IEEE754
Double
void
Void
Les valeurs maximale et minimale de chaque type peuvent être obtenues en utilisant des
constants de la classe associée, par exemple :
Byte.MIN_VALUE
Integer.MAX_VALUE
Float.MIN_EXPONENT
1.5 Nombre à grande précision
Java inclut deux classes pour faire de l'arithmétique de grande précision :
Java avancé ■ 3
► BigInteger permet la représentation intégrale de valeur de n'importe quelle taille sans
perte d'information durant les opérations.
► BigDecimal est pour des nombres arbitraires à virgule fixe (une représentation
utilisant un nombre fixe de chiffres après la virgule et un facteur d'échelle. Chaque bit
du facteur d'échelle représente l'inverse d'une puissance de 10 (ou de 2). Ainsi, la
première décimale est 1/10, la seconde 1/100, etc. Par exemple 1.23 sera représenté par
1230 avec un facteur d'échelle de 1/1000. Cette représentation permet d'augmenter la
vitesse d'exécution des opérations de calcul).
1.6 Utilisation de label à l'intérieur de boucles
Parce qu'il n'y a pas de goto, Java utilise des labels pour sortir des boucles imbriquées.
1.7 L'opérateur virgule
L’opérateur virgule a seulement deux usages : dans la déclaration des variables ou des
arguments et dans une expression de boucle for.
int x, y;
for (int i=0, j=0; i<10; i++, j++) { ... }
1.8 La boucle infinie
Le compilateur traite de la même façon la boucle infinie while (true) et for(;;), y compris
pour l'optimisation.
2. Initialisation et nettoyage
2.1 Signature de méthodes
La valeur de retour ne fait pas partie de la signature d'une méthode qui comprend
seulement le nom de la méthode et la liste des arguments. Les deux déclarations suivantes
provoquent une erreur de compilation :
class toto {
int f();
void f();
}
ENSICAEN - Spécialité Informatique
label1:
boucle-externe {
// e.g. for (int i=0; i<10; i++)
boucle-interne {
…
break;
// arrête la boucle interne et retourne à la boucle
externe
…
continue; // continue au début de la boucle interne
…
continue label1; // stoppe la boucle interne
//et poursuit la boucle externe
…
break label1; // stoppe à la fois les boucles interne et externe
…
}
}
4 ■ Java avancé
De la même façon, la spécification d'exception ne fait pas non plus partie de la signature
des méthodes. Les deux déclarations suivantes produisent une erreur de compilation :
class toto {
int f() throws IOException;
int f() throws RuntimeException;
}
2.2 La méthode Finalize
La méthode finalize() de la classe Object n'est pas utilisée pour libérer la mémoire.
Cette méthode est appelée avant que l'objet soit libéré, mais on ne peut pas savoir quand,
puisque le ramasse-miette (garbage collector) est utilisé seulement quand cela est nécessaire.
Si un objet doit être supprimé (autrement que par le ramasse-miette), il faut utiliser les
idiomes suivants : initialiser l'objet et immédiatement entrer dans un bloc 'try-catch' pour
ajouter le code qui sera utilisé par l'objet, et utiliser la clause finally pour effectuer le
nettoyage après avoir utilisé l'objet.
ENSICAEN - Spécialité Informatique
Foo f = new Foo();
try {
bar(f);
} catch() {
// si nécessaire
} finally {
// ajouter du code de nettoyage
}
2.3 Initialisation de données membres
Quand un type de donnée primitif est membre d'une classe, il est garantie d'avoir une
valeur par défaut s'il n'est pas initialiser explicitement. La valeur par défaut correspond à la
valeur 0 (eg., booléen = faux, référence = null, int = 0, string = null).
class Foo {
int x ;
// x est initialisé à 0
Bar bar;
// bar est initialisé à null
}
Cette garantie ne s'applique pas aux variables locales.
2.4 Ordre d'initialisation
À l'intérieur d'une classe, l'ordre d'initialisation est déterminé par l'ordre dans lequel les
variables sont déclarées.
2.5 Initialisation statique explicite
Java permet de grouper des initialisations statiques à l'intérieur d'un bloc spécial dans
une classe. Il ressemble à celui-ci :
class Spoon {
static int i;
static Cup c1;
static {
i = 47;
c1 = new Cup();
System.out.println(“C1 et I initialisés”);
Java avancé ■ 5
}
}
Ce bloc est exécuté une fois : la première fois que l'on crée un objet de cette classe ou la
première fois que l'on exécute une méthode statique de cette classe (en fait, quand l'unité de
compilation correspondante est chargée).
2.6 Initialisation non-statique d'instance
Java fournit un bloc similaire anonyme pour initialiser les variables non-statiques de
chaque objet :
class Mugs {
private Mug c1;
// bloc d'initialisation
{
c1 = new Mug();
System.out.println(“c1 initialized”);
}
}
Il ressemble exactement au bloc d'initialisation statique mais sans le mot clé static. Le
bloc est exécuté à chaque fois qu'une instance est créée.
2.7 L'objet Class
Chaque fois que l'on écrit et compile une nouvelle classe, un objet Class est aussi créé et
stockée, dans un fichier de même nom et suffixé .class. À l'exécution, quand on veut faire
un objet de cette classe, la machine virtuelle Java qui exécute le programme doit d'abord
vérifier si l'objet Class pour ce type est chargée. Si non, la JVM la charge en recherchant le
fichier .class avec ce nom. Donc, un programme Java n'est pas complètement chargé avant
de commencer à s'exécuter, ce qui diffèrent de tous les autres langages.
2.8 Constructeur
Une classe possède au moins un constructeur par défaut (constructeur avec aucun
argument) si le développeur n'en définit aucun. Cependant, si le développeur définit au
moins un constructeur avec arguments, alors le constructeur par défaut n'est pas défini, et il
n'est pas possible de faire une instance avec le constructeur par défaut.
Java insère automatiquement l'appel au constructeur de la classe de base (super()) dans
le constructeur des classes dérivées. Cependant, il est possible d'appeler explicitement un
autre constructeur de la classe parent à l'aide de la méthode super() avec les bons
arguments. Dans ce cas, elle doit être la première instruction du constructeur. Si le
constructeur par défaut de la classe de base n'est pas défini alors la machine virtuelle lève
une exception.
class Candy {
public Candy() { // method superflue
super(); // appel superflu
}
}
De la même façon, il est possible d'appeler un autre constructeur de la classe avec la
méthode this(). Toutefois, elle doit être la première instruction du constructeur. Donc, il
n''est pas possible de cumuler l'appel à différents constructeurs à l'intérieur d'un même
ENSICAEN - Spécialité Informatique
C'est une façon d'ajouter un bloc d’initialisation commun à tous les constructeurs.
6 ■ Java avancé
constructeur. Dans ce cas, on peut utiliser le bloc d'initialisation non statique.
3. Cacher l'implémentation
3.1 La spécification d'accessibilité
Il y a quatre types d'accessibilité des attributs et des méthodes :
► privée (private)
► protégé (protected)
► paquet (package)
► public (public)
Quand on ne spécifie aucune accessibilité, cela revient à l’accessibilité de type paquet qui
correspond à une accessibilité de type « amie » (friend en C++). Cela signifie que toutes les
autres classes du même paquet ont accès aux membres de la classe, mais pour toutes les
autres classes en dehors du paquet, les membres apparaissent comme privés.
ENSICAEN - Spécialité Informatique
Une autre façon de faire une classe amie d'une classe, dans le sens C++, est de mettre la
classe amie dans la seconde classe comme classe interne. Ainsi, elle peut accéder aux
membres privés membres de la seconde classe.
Le mot clé protected donne aussi l'accès aux paquets. En Java, il n'est pas possible de
distinguer entre l'accessibilité par dérivation et accessibilité par paquet. Cependant, une
bonne pratique est de garder la distinction dans le code : une réduction de dérivation utilise
la spécification de type protégé et une réduction de paquet n'utilise aucune spécification.
4. Réutiliser les classes
4.1 Redéfinition
Le C++ et le C# n'implémente pas la surcharge de méthode par défaut, à savoir qu'une
méthode d'une classe de base avec un prototype donné peut être redéfinie dans une classe
dérivée. Java au contraire autorise la surcharge automatique. Pour se prémunir contre ce qui
peut être une erreur de renommage, en Java vous devez ajouter l'annotation @Override audessus de la déclaration d'une méthode redéfinie et ainsi le compilateur génère une erreur si
le prototype diverge de celui de la méthode à redéfinir.
De même, si une méthode de base est déclaré privée, elle n'est pas visible dans une classe
dérivée, et si le programmeur défini une méthode avec le même nom de base dans la classe
dérivée, c'est une nouvelle méthode de la classe dérivée.
4.2 Le mot clé « final »
4.2.1 Attribut « final »
Cela correspond à la déclaration d'une constante.
private final int a = 5;
Java avancé ■ 7
4.2.2 Attribut « final » blanc
Un attribut final blanc est un attribut qui est déclaré comme final (donc constant) mais
dont la valeur n'est pas fournie au moment de la définition. Sa valeur sera donnée plus tard
eau moins avant sa première utilisation, et le compilateur vérifie cela.
Cela permet d'avoir un attribut constant dans une classe qui peut avoir des valeurs avec
différentes selon les objets, mais avec une qualité de constante. La valeur est donnée la
première fois qu'elle est affectée.
class Foo {
final int _a;
Foo() { _a = 1; }
Foo( int val ) { _a = val; }
bar() {
_a = 2; // erreur de définition de constante levée à la compilation }
}
4.2.3 Méthode « finale »
Il y a deux raisons pour définir des méthodes finales :
► sécurité : pour empêcher les redéfinitions de la méthode.
4.2.4 Classe « finale »
Pour des raisons de sécurité et de sûreté, il peut être interdit de pourvoir construire des
classes qui héritent d'une classe donnée. Dans l'exemple suivant, la classe FooBar ne peut
plus être dérivée.
public final class FooBar{ }
4.2.5 Argument « final »
Un argument final ne peut pas être modifié, mais il est possible de changer l'objet
référencé par l'argument lorsqu'il s'agit d'une référence sur un objet. En Java, il n'y a aucun
moyen d’empêcher cela (contrairement à C++).
void with( final Gizmo g ) {
g = new Gizmo(); // Erreur
g.x = 2; // pas d'erreur
}
5. Interfaces et classes internes
5.1 Interface
Une interface peut contenir des attributs, mais qui doivent être static et final (constant).
C'est utile pour la gestion de projet quand une interface est utilisée entre deux équipes de
développements et quelques constantes doivent être définies.
interface instrument {
int i=5;
// static & final
void play( Note n ); // automatically public
ENSICAEN - Spécialité Informatique
► efficacité : permettre au compilateur de remplacer tout appel d'une méthode par son
code (inline method). Il faut l'utiliser et en abuser.
8 ■ Java avancé
String what();
}
Ainsi, une interface est un moyen de créer des constantes (mais on peut préférer les
énumérations).
public interface Months {
int JANUARY = 1, FEBRUARY = 2, MARCH = 3, APRIL = 4, MAY = 5, JUNE = 6,
JULY = 7, AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10,
NOVEMBER = 11, DECEMBER = 12;
}
Ces constantes peuvent être initialisées avec des expressions non constantes. Par
exemple :
public interface RandVals {
int rint = (int)(Math.random() * 10);
long rlong = (long)(Math.random() * 10);
float rfloat = (float)(Math.random() * 10);
double rdouble = Math.random() * 10;
}
5.2 Classe de niveau supérieur
ENSICAEN - Spécialité Informatique
On déclare une classe de niveau supérieur par unité de compilation. Chaque classe de
niveau supérieur correspond donc à son propre fichier Java portant son nom. C'est aussi la
seule classe publique de l'unité de compilation.
5.3 Classe interne
Une classe interne est une classe définie à l'intérieur d'une classe publique. Dépendant
de la façon dont elle est définie, une classe interne peut être d'un des quatre types :
1/ Une classe interne locale.
2/ Une classe interne statique.
3/ Une classe interne anonyme.
4/ Une classe membre.
5.3.1 Classe interne locale
Les classes locales sont équivalentes aux variables locales, dans le sens où elles sont
créées et utilisées à l'intérieur d'un bloc. Une fois que l'on déclare une classe dans un bloc,
elle peut être instanciée autant de fois que souhaité dans ce bloc. Comme les variables
locales, les classes locales ne sont pas autorisées à être déclarées publiques, protégées,
privées ou statiques.
Voici un exemple de code :
public class Parcel1 {
class ListListener implements ItemListener {
List list;
public ListListener( List l ) {
list = l;
}
@Override
public void itemStateChanged( ItemEvent e ) {
String s = l.getItemSelected();
doSomething(s);
Java avancé ■ 9
}
}
List list1 = new List();
list list2 = new List();
list1.addItemListener(new ListListener(list1));
list2.addItemListener(new ListListener(list2));
}
}
5.3.2 Les classes internes statiques
Les classes internes ont accès à la classe englobante (toutes les méthode et tous les
attributs). Si vous n'avez pas besoin d'une relation entre la classe interne et les objets de la
classe externe, alors vous pouvez faire une classe interne statique (appelée classe imbriquée).
Dans ce cas seulement, la méthode et les champs sont accessibles par d'autres classes que la
classe externe et sans création d'un objet de la classe externe. Il est également possible de
créer des objets de la classe imbriquée sans créer d''objet de la classe externe.
Classe de niveau supérieur imbriquée
Si votre classe principale a quelques classes d'assistance plus petites qui peuvent être
utilisées en dehors de la classe principale elle-même et ne font sens qu'avec votre classe
principale, c'est une bonne idée d'en faire des classes de niveau supérieur imbriquées. Pour
utiliser la classe imbriquée de haut niveau, il faut écrire : TopLevelClass.NestedClass. En
Swing, c'est le cas de classes telles que Point2D.Float ou Point2D.Double qui sont
intégrées à la classe Point2D.
Voici un autre exemple :
public class Filter {
ArrayList<Criterion> criteria = new ArrayList<Criterion>();
public addCriterion( Criterion c ) {
criteria.addElement(c);
}
public boolean isTrue( Record rec ) {
for(Enumeration e=criteria.elements(); e.hasMoreElements();) {
if(! ((Criterion)e.nextElement()).isTrue(rec)) {
return false;
}
}
return true;
}
public static class Criterion {
String colName, colValue;
public Criterion( String name, String val ) {
colName = name; colValue = val;
}
public boolean isTrue( Record rec ) {
String data = rec.getData(colName);
if(data.equals(colValue)) { return true; }
return false;
ENSICAEN - Spécialité Informatique
Une classe de niveau supérieur imbriquée est une classe membre avec le modificateur
static. Une classe de niveau supérieur imbriquée est une classe comme n'importe quelle
autre classe de niveau supérieur, sauf qu'elle est déclarée dans une autre classe ou interface.
Une classe de niveau supérieur imbriquée est généralement utilisée comme un moyen
pratique de regrouper des classes en relation sans la création d'une nouvelle unité de
compilation.
10 ■ Java avancé
}
}
}
Et quand on veut l'utiliser :
Filter f = new Filter();
f.addCriterion(new Filter.Criterion("SYMBOL", "SUNW"));
f.addCriterion(new Filter.Criterion("SIDE", "BUY"));
.....
if(f.isTrue(someRec)) //do some thing ...
Remarques
► Il est impossible de déclarer une classe de niveau supérieur de type statique. Cela n'a
pas de sens en Java.
► Il est possible pour cela d'utiliser une classe imbriquée dans une interface.
ENSICAEN - Spécialité Informatique
interface IInterface {
static class Inner {
int i, j, k;
public Inner() {}
void f() {}
}
}
Pour référencer l'objet correspondant à la classe externe à l'intérieur d'une méthode de la
classe interne, il faut utiliser Class.this.
public class Parcel1 {
class ListListener implements ItemListener {
List list;
public ListListener( List l ) {
list = l;
}
@Override
public void itemStateChanged( ItemEvent e ) {
String s = l.getItemSelected();
doSomething(Parcel1.this);
}
}
List list1 = new List();
list list2 = new List();
list1.addItemListener(new ListListener(list1));
list2.addItemListener(new ListListener(list2));
}
}
Quelques fois, on souhaite créer des objets d'une des classes internes non statiques. Pour
faire cela, on doit fournir une référence aux autres objets des classes externes dans
l'expression de la création par new comme cela :
public class Parcel2 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination( String whereTo ) {
label = whereTo;
}
Java avancé ■ 11
}
String readLabel() { return label; }
public static void main( String[] args ) {
Parcel2 p = new Parcel2();
Parcel2.Contents c = p.new Contents();
Parcel2.Destination d = p.new Destination("Tanzania");
}
}
La profondeur d'imbrication d'une classe importe peu :
class MMA {
private void f() {}
class A {
private void g() {}
public class B {
void h() {
g();
f();
}
}
}
}
}
mnaab.h();
}
}
Il est possible de faire de l'héritage de classes internes statiques :
class WithInner {
class Inner {}
}
public class InheritInner extends WithInner.Inner {
InheritInner(WithInner wi) {
wi.super();
}
public static void main( String[] args ) {
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
5.3.3 Classe interne anonyme
Les classes anonymes sont déclarées et instanciées dans la même instruction. Elles n'ont
pas de nom, et ne peuvent instanciées qu'une seule fois.
Ce qui suit est un exemple d'une classe anonyme. L'exemple définit une classe dérivée
anonyme de la classe ActionListener qui redéfinit la méthode actionPerformed().
okButton.addActionListener( new ActionListener() {
@Override
public void actionPerformed( ActionEvent e ) {
dispose();
}
});
Parce qu'une classe anonyme n'a pas une déclaration normale ù il est possible de
d'introduire le mot clé static, elle ne peut pas être déclarée statique.
ENSICAEN - Spécialité Informatique
public class MultiNestingAccess {
public static void main( String[] args ) {
MNA mna = new MNA();
MNA.A mnaa = mna.new A();
MNA.A.B mnaab = mnaa.new B();
12 ■ Java avancé
Une méthode qui retourne une classe interne anonyme :
public class Parcel3 {
public Contents cont() {
return new Contents() {
private int i = 11;
public int value() { return i; }
}; // point-virgule obligatoire
}
public static void main( String[] args ) {
Parcel3 p = new Parcel3();
Contents c = p.cont();
}
}
Lorsque l'on définit une classe interne anonyme et que l'on veut utiliser un objet d'une
classe englobante de la classe interne anonyme, le compilateur impose que l'argument
référencé soit de type final.
ENSICAEN - Spécialité Informatique
public class Parcel8 {
public Destination dest( final String dest ) {
return new Destination() {
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main( String[] args ) {
Parcel8 p = new Parcel8();
Destination d = p.dest("Tanzania");
}
}
Si on veut une initialisation anonyme pour créer un constructeur pour une classe interne
anonyme, il faut utiliser le bloc d'initialisation anonyme :
public class Parcel9 {
public Destination dest(final String dest, final float price) {
return new Destination() {
private int cost;
{ // A l'intérieur du bloc d'initialisation de l'instance
cost = Math.round(price);
if (cost > 100) {
System.out.println("Over budget!");
}
}
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main( String[] args ) {
Parcel9 p = new Parcel9();
Destination d = p.dest("Tanzania", 101.395F);
}
}
5.3.4 Classe membre
Les classes membres sont définies à l'intérieur du corps d'une méthode. On peut déclarer
des classes de membres partout dans le corps de la classe externe. Elles sont utiles quand on
souhaite utiliser des variables et des méthodes de la classe externe sans délégation explicite.
Java avancé ■ 13
public class Parcel4 {
public Destination dest(String s) {
class Pdestination implements Destination {
private String label;
private Pdestination( String whereTo ) {
label = whereTo;
}
public String readLabel() { return label; }
}
return new PDestination(s);
}
public static void main( String[] args ) {
Parcel4 p = new Parcel4();
Destination d = p.dest("Tanzania");
}
}
Lorsque l'on déclare une classe membre, il n'est possible d'instancier cette classe membre
que dans le contexte d'un objet de la classe externe dans laquelle cette classe membre est
déclarée. Si vous souhaitez supprimer cette restriction, vous déclarez la classe membre
comme une classe statique. Lorsque l'on déclare une classe membre avec le modificateur
static, elle devient une classe de niveau supérieur imbriquée et peut être utilisée comme
une classe de niveau supérieur normal.
6.1 Exceptions
Les exceptions font partie intrinsèque de la programmation orientée objet et doit être
utilisée intensivement. Le langage Java est basé sur les exceptions et le compilateur oblige à
les utiliser.
6.1.1 Définition
Une condition exceptionnelle est un problème qui empêche la poursuite d'une méthode
ou d'une partie de code. Il est important de distinguer une condition exceptionnelle, d'un
problème normal, dans lequel vous avez suffisamment d'information dans le contexte actuel
pour faire face au problème. Avec une condition d'exception, vous ne pouvez pas continuer
le traitement parce que vous n'avez pas les informations nécessaires pour régler le problème
dans le contexte actuel.
Une directive importante dans la gestion des exceptions est "ne pas attraper une
exception, sauf si vous savez quoi faire avec elle."
6.1.2 Signature de méthode
Bien que la spécification d'exception soit imposée par le compilateur lors de l'héritage, la
spécification d'exception ne fait pas partie de la signature de méthode, qui ne comprend que
le nom de la méthode et la liste des arguments. De plus, il est possible d'utiliser une classe
d'exception dérivée dans les méthodes redéfinies comme dans l'exemple ci-dessous :
public class H {
public void a() throws IOException {
}
}
ENSICAEN - Spécialité Informatique
6. Gestion des erreurs avec les exceptions
14 ■ Java avancé
public class Test extends H {
public void a() throws EOFException {
}
}
Cela est possible puisque EOFException est une classe dérivée de IOException.
6.1.3 Clause Try Catch
Les exceptions sont avalées par la première clause catch la plus proche dans l'ordre dans
lequel elles ont été écrites. La plus proche doit être compris selon le sens de l'héritage.
6.1.4 La classe Throwable
La classe Throwable est la superclasse de toutes les erreurs et toutesles exceptions dans
le langage Java. Il y a deux classes dérivées : Error et Exception.
Une Error indique un problème sérieux qu'une application ne devrait raisonnement pas
capturer dans un try-catch. Cet type d'exception est révélateur d'une erreur dans le
programme.
ENSICAEN - Spécialité Informatique
Une 'Error' indique de sérieux problèmes qu'une application raisonnable ne devrait pas
essayer de récupérer. Une Exception doit être gérée dans un 'try-catch', (exception faite
pour les exceptions de type RuntimeException). Le compilateur vous oblige à cela.
Les méthodes de la classe Throwable :
► String getMessage()
► String getLocalizedMessage: Récupérer le message détaillé dans une langue
considérée.
► void printStackTrace()
► Throwable fillStackTrace() : enregistre dans cet objet les informations sur l'état
actuel de pile. C'est utile quand une application réémet une erreur ou une exception à
partir de cette exception. On garde ainsi trace du contexte de l'erreur et pas celui de la
réémission.
6.1.5 L'exception Throwable
C'est la super-classe de toutes les exceptions. C'est une exception qui n'est pas récupérée
par un catch.
public static void main() thows Throwable ( String args[]) {
try {
throw new Throwable();
} catch (Exception e) {
}
}
6.1.6 Passer les exceptions à la console
public static void main( String args[] ) thows IOException {
FileInputStream f = new FileInputStream(“main.txt”);
f.close();
}
Java avancé ■ 15
6.1.7 Convertir une exception
Si « je n'ai aucune idée de quoi faire avec cette exception, mais je ne veux pas l'avaler ou
afficher un message banal », on peut transformer cette exception en erreur de type
RuntimeException.
try {
...
} catch (Exception e) {
throw new RuntimeError(e);
}
6.2 La clause « finally »
Dans la classe try-catch, qu'une exception soit levée ou pas, la clause finally est toujours
exécutée.
try {
// Activités dangereuses qui peuvent lever les exceptions A, B or C
A
B
C
exécuté.
7. Interroger les types (Run-Time Type Identification)
Le but des RTTI est de permettre de déterminer le type d'un objet quand on ne possède
qu'une référence de la classe de base vers cet objet.
7.1 L'opérateur 'instanceof'
L'opérateur “instanceof” retourne vrai si l'objet sur lequel on l'applique est de la classe
spécifiée et pas une de ses super-classes. Il retourne faux si on lui passe un objet null.
Foo x = null;
Foo y = new FooBar();
x instanceof Foo;
y instanceof Foo;
y instanceof FooBar;
//
//
//
//
FooBar est un classe dérivée de Foo.
false parce que x est null
true
true
7.2 Équivalent de l'opérateur instanceof
Deux méthodes permettent de remplacer le instanceof: class.isInstance(Object) et
Object.getClass(). L'instruction objet.class contient les informations sur la classe et ses
instances.
Base x = new Derived();
Base.class.isInstance(x)
x.getClass() == Base.class
x.getClass().equals(Base.class)
x.getClass() == Derived.class
→
→
→
→
true
false puique x.getClass() ="Derived"
false
true
ENSICAEN - Spécialité Informatique
} catch (A a) {
// Code pour l'exception
} catch (B b ) {
// Code pour l'exception
} catch (C c) [
// Code pour l'exception
} finally {
// Code qui est toujours
}
16 ■ Java avancé
7.2.1 Avantage
for (int i = 0; i < pets.size(); i++) {
Object o = pets.get(i);
if (o instanceof Cat)
((Counter)h.get("class Cat")).i++;
if (o instanceof Dog)
((Counter)h.get("class Dog")).i++;
…
}
La nouvelle version a éliminé le besoin de l'opérateur instanceof et on peut l'utiliser
dynamiquement dans une boucle.
petTypes[0] = Cat.class;
petTypes[1] = Dog.class;
for (int i = 0; i < pets.size(); i++) {
Object o = pets.get(i);
for (int j = 0; j < petTypes.length; ++j) {
if (petTypes[j].isInstance(o)) {
…
}
}
}
ENSICAEN - Spécialité Informatique
7.3 Le paquet « Reflection » : informations dynamiques sur les classes
Le paquet reflection supporte le concept de reflection. Il apporte les classes Field,
Method et Constructor qui implémentent chacune l'interface Member :
► String Class.getName()
► Class[] Class.getInterfaces()
► boolean Class.isInterface()
► Class Class.getSuperclass()
7.3.1 Chargement dynamique de classe
L'instruction Class.forName(String) charge dynamiquement une classe dans un
programme
Class c = Class.forName(“c10.Dog”);
Object = c.newInstance();
8. Collections d'objets
8.1 Les classes String et StringBuffer
La classe StringBuffer est une classe associée à la classe String. La classe String est
une classe immuable alors que stringBuffer est une classe modifiable. Avec StringBuffer
toutes les modifications portent sur l'objet lui-même, et non sur une copie de l'objet comme
pour String.
8.2 Tableau
Le tableau est le moyen le plus efficace pour stocker et accéder aléatoirement à une
Java avancé ■ 17
séquence de références d'objet. Vous obtenez des vérifications des limites, indépendamment
de savoir si vous utilisez un tableau ou un conteneur.
Quand un tableau est créé en tant que donnée membre, ses références sont
automatiquement initialisées à null. De même, un tableau Java est assuré d'être initialisé et
ne peut être accessible en dehors de ses bornes. Lorsqu'un index est hors limite, Java lève
une exception de type ArrayIndexOutOfBoundsException.
8.2.1 Définition de tableau
int[] a; // Notation préférée
int a[];
8.2.2 Tableau mufti-dimensionnel
Première forme d'initialisation :
int[][] a1 = {
{ 1, 2, 3, },
{ 4, 5, 6, },
};
Seconde forme d'initialisation :
Troisième forme d'initialisation :
int[][][] a3 = new int[pRand(7)][][];
for (int i = 0; i < a3.length; i++) {
a3[i] = new int[pRand(5)][];
for (int j = 0; j < a3[i].length; j++) {
a3[i][j] = new int[pRand(5)];
}
}
d'autres formes d'initialisation :
Weeble[] a; // a = null.
a = new Weeble[] {
new Weeble(), new Weeble()
};
Weeble[] b = new Weeble[5]; // each Weeble[i] == null;
Weeble[] c = new Weeble[4];
for(int i = 0; i < c.length; i++)
c[i] = new Weeble();
Weeble[] d = {
new Weeble(), new Weeble(), new Weeble()
};
int[] e;
int[] f = new int[5];
int[] g = new int[4];
for (int i = 0; i < g.length; i++)
g[i] = i*i;
int[] h = { 11, 47, 93 };
hide(d); or hide(new Weeble[]{ new Weeble(), new Weeble()});
Pour des raisons d'efficacité, il est toujours préférable d'utiliser des tableaux
monodimensionnels plutôt que multidimensionnels. Le gain en temps d'exécution est
réellement très conséquent. Il suffit de refaire l'accès aux indices par calcul. Par exemple, la
version multidimensionnelle est :
int[][] a1 = new int[15][5]
for (int i=0; i<15; i++) {
ENSICAEN - Spécialité Informatique
int[][][] a2 = new int[2][2][4]; // → a2[0][0][0] = 0
18 ■ Java avancé
for (int j=0; j<5; j++) {
a1[i][j]=1;
}
}
et la version monodimensionnelle équivalente est :
int[] a2 = new int[15*5]
for (int i=0; i<15*5; i++) {
a2[i/5][j%5]=1;
}
8.2.3 La classe Arrays
La classe arrays est une classe utilitaire qui contient plusieurs méthodes statiques :
► boolean equals() : pour comparer 2 tableaux.
► void fill() : remplir le tableau avec une valeur donnée.
► void sort() : tri le tableau.
► boolean binarySearch() :nécessite un tableau trié.
► List asList() : retourne un tableau sous forme de liste.
ENSICAEN - Spécialité Informatique
Pour copier un tableau, il faut utiliser la méthode System.arrayCopy(). Pour un
tableau d'objets cela ne copie que les références des objet et pas leur contenu.
La recherche dans un tableau nécessite d'implémenter l'interface Comparator qui oblige
à définir la méthode Compare().
public class AlphabeticComparator implements Comparator {
public int compare( Object o1, Object o2 ) {
String s1 = (String)o1;
String s2 = (String)o2;
}
return s1.toLowerCase().compareTo(s2.toLowerCase());
}
AlphabeticComparator comp = new AlphabeticComparator();
Arrays.sort(sa, comp);
int index = Arrays.binarySearch(sa, sa[10)], comp);
Java avancé ■ 19
8.3 Conteneur
Les conteneurs sont représentés par 2 classes de base :
1/ Collection : un groupe d'éléments individuels. La classe associée est Collections avec
ses méthodes statiques correspondant à des utilitaires de manipulation de collection.
Il y a deux classes abstraites dérivées :
1/ List
► ArrayList : une liste implémentée comme un tableau. Elle permet un accès rapide
aux éléments, mais est lente quant à l'insertion ou la suppression d'éléments dans le
milieu de la liste.
► LinkedList : fournit un conteneur avec un accès séquentiel, avec des insertions et
des suppressions rapides dans le milieu de la liste. Par contre, il est relativement
lent pour un accès aléatoire.
► Remarque : Vector, Stack sont synchronisés ce qui rend ces deux conteneurs lents à
l'utilisation. Quand la synchronisation n'est pas nécessaire, il est préférable d'utiliser
ArrayList pour les vecteur et LinkedList pour les piles.
2/ Set : chaque élément est garantie d'être unique. Les éléments doivent définir la
méthode hashCode(). (voir la section 8.3.1)
► HashSet : pour les ensembles où la recherche d'élément est importante. Ce doit être
la classe par défaut pour les ensembles de type Set.
► LinkedHashSet : équivalent à HashSet mais maintient l'ordre dans lequel on insère
les éléments.
► TreeSet : un ensemble ordonné avec l'algorithme du tri tas. L'ordre entre les
éléments est donné par la méthode de comparaison des éléments equals().
ENSICAEN - Spécialité Informatique
Figure 1: La hiérarchie des conteneurs.
20 ■ Java avancé
2/ Map : un tableau associatif contenant des paires d'objet de type clé-value. Il y a deux
classes dérivées abstraites. Il nécessite d'implémenter la méthode hashCode() et de
définir la méthode equals().
1/ AbstractMap : elle définit les
constainsValue, size, isEmpty.
opérations
de
base :
put,
get,
containsKey,
► HashMap : à utiliser la place de Hashtable, parce qu'il n'est pas synchronisé et donc
est plus rapide.
► LinkedHashMap : le tableau peut être parcouru comme une LinkedList. On
récupère les pairs dans l'ordre d'insertion, ou dans l'ordre donné par la date d'accès
des éléments.
2/ SortedMap
► TreeMap : implémentation basée sur les arbres rouges et noirs. Il permet de garder
le contenu trié.
► IdentityHashMap : permet d'utiliser == au lieu de equals et est donc plus rapide.
Comme les interfaces Set et List, l'interface Map nécessite la rédéfinition des méthodes
equals() et hashCode() de telle manière que deux objets puissent être comparés de
manière logique sans regarder leur implémentation.
ENSICAEN - Spécialité Informatique
8.3.1 Code de hachage
Dans son livre “Effective Java”, Joshua Bloch donne une recette pour générer un code de
hachage consistant pour la méthode hashCode():
1/ Définir une variable entière nommée result avec une constante, par exemple 17.
2/ Pour chaque attribut de la classe (en fait les attributs qui sont utilisés par la méthode
equals(), il faut calculer un code de hachage c comme suit :
Type de la variable
Calcul
boolean f
c=(f? 0:1)
byte, char, short, int f
c=(int)f;
long f
c=(int)(f>>>32))
float f
c=Float.floatToIntBits(f);
double long l
Double.doubleToLongBits(f);
c=(int)(l^(l>>>32))
Object f
c = f.hashCode()
Array f
Appliquer les régles précédentes sur chaque élément.
3/ Combiner les codes hachages calculés au-dessus : result = 37*result+c;
4/ Retourner result.
Exemple
public class XXX {
private String s;
private int id;
public int hashCode() {
Java avancé ■ 21
int result =17;
result = 37*result + s.hashCode();
result = 37*result + id;
}
public equals( Object o ) {
return (o instanceof XXX) && s.equals(((XXX o).s) && id = ((XXX o).id);
}
8.4 Rendre une collection ou une map non-modifiable
Il faut utiliser la classe Collections et ses méthodes :
List a = Collections.unmodifiableList(new ArrayList());
a.add(“ttto”'); // error UnsupportedOperationException
Set s = Collections.unmodifiableSet(new HastSet());
s.add(“eehfg”); // error UnsupportedOperationException
map m = Collections.unmodifiableMap(new hashMap());
m.put(“”e”);
// error UnsupportedOperationException
8.5 Synchroniser une Collection ou une Map
Basique :
Une version plus protégée utilise les méthodes de la classe Collections :
List l = Collections.synchronizedList(new ArrayList());
Set s= Collections.synchronizedSet(new HashSet());
Map m = Collections.synchronizedMap(new HashMap());
8.6 Copie profonde
Les deux classes CopyOnWriteArrayList et CopyOnWriteArraySet crée des copies
distingues d'une collection.
8.7 Gestion des références
La bibliothèque java.lang.ref contient un ensemble de classes qui permettent une plus
grande souplesse dans la gestion du ramasse-miettes. Ces classes sont particulièrement utiles
pour le cas d'objets qui consomment beaucoup de mémoire. Chacune fournit différents
niveaux d’indirection pour le ramasse-miette si l'objet en question est accessible par un des
objets de référence ci-dessous :
► SoftReference
► WeakReference
► PhantomReference
On utilise des objets de ces classes lorsque l'on souhaite conserver une référence sur un
objet. On souhaite être en mesure d'accéder à cet objet, mais on souhaite aussi permettre au
ramasse-miette de libérer cet objet si la mémoire vient à manquer.
On accomplit cela en utilisant une référence intermédiaire entre l'objet et sa référence, et
il doit y avoir pas de références classiques à l'objet. Vous pouvez utiliser la référence comme
un cache. C'est une implémentation du patron de conception proxy.
ENSICAEN - Spécialité Informatique
synchronized(list) {
}
22 ■ Java avancé
8.8 Ensemble de bits
La classe BitSet implémente un vecteur de bits qui augmente dynamiquement. Un BitSet
est utilisé si vous voulez stocker efficacement un grand nombre d'informations binaires. Il
est efficace seulement du point de vue de la taille, au détriment de la vitesse d'accès. Il est
légèrement plus lent que l'utilisation d'un tableau d'un type natif.
BitSet x = new BitSet(1024);
La taille minimale de BitSet est celle d'un long : 64 bits. Cela signifie que si vous stockez
quelque chose de plus petit, comme 8 bits, un BitSet perd beaucoup de place. Il est plus
efficace de créer sa propre classe, ou tout simplement d'utiliser un tableau si la mémoire est
un problème.
Exemple d'utilisation :
ENSICAEN - Spécialité Informatique
BitSet bb = new BitSet();
Byte bt = (byte)rand.nextInt();
for (int i = 7; i >= 0; i-- ) {
if (((1<<i) & bt ) != 0) {
bb.set(i);
} else {
bb.clear(i);
}
}
String bbits = new String();
for (int j=0; j<8; j++ ) {
bbits += (bb.get(j) ? “1” : “0”);
}
System.out.println(“bit pattern =” + bbits);
9. Sérialisation
La sérialisation est intéressante car elle permet d'implémenter la persistance légère. Cela
signifie que la durée de vie d'un objet dépasse celle du temps d’exécution du programme. Il
faut noter qu'aucun constructeur, pas même le constructeur par défaut, est appelée dans le
processus de désérialisation d'un objet de type Serializable.
9.1 Le mot clé transient
Les objets sérialisables sont ceux qui implémentent l'interface Serializable alors que
les non-sérialisables sont marqués avec le mot-clé transient. Pour éviter que des parties
sensibles d'un objet serialisable ne soient sérialisées, il faut ajouter sur chacun des attributs à
protéger le mot clé transient qui dit "ne pas de soucier de sauver ou restaurer ces attributs,
je m'en occupe."
private transient String _secretCode;
9.2 Controlling serialization
Par défaut, Java prend en charge la sérialisation d'objets par un code binaire propriétaire,
mais il fournit également un mécanisme soit pour (1) étendre ce format initial
(readObject() et writeObject()) ou (2) réécrire le processus de sérialisation (Interface
Externisable).
Java avancé ■ 23
9.2.1 L'interface Serializable
Les classes qui nécessitent un traitement spécial au cours des processus de sérialisation et
de désérialisation doivent mettre en œuvre des méthodes spéciales avec les signatures
suivantes :
private void writeObject(java.io.ObjectOutputStream out) throws
IOException
private void readObject(java.io.ObjectInputStream in) throws IOException,
ClassNotFoundException;
private void readObjectNoData() throws ObjectStreamException;
On remarquez qu'elles sont définies comme privées, ce qui signifie qu'elles ne sont
appelées que par d'autres membres de cette classe. Cependant, elles ne sont pas réellement
appelées par d'autres membres de cette classe, mais par les méthodes writeObject()
readObject() des objets ObjectOutputStream et ObjectInputStream. On peut se
demander comment les objets ObjectOutputStream et ObjectInputStream ont accès à des
méthodes privées de votre classe. Considérons simplement que c'est la magie de la
sérialisation Java.
9.2.2 L'interface Externalizable
public interface Externalizable {
private void readObject( ObjectInputStream stream ) throws IOException,
ClassNotFoundException;
private void writeObject( ObjectOutputStream stream ) throws
IOException, ClassNotFoundException;
}
10. Concurrence
10.1 Résoudre les conflits des ressources partagées
10.1.1 Méthode Critique
Une ressource partagée est généralement juste un morceau de la mémoire sous la forme
d'un objet, mais elle peut également être un fichier, un port d'entrée/sortie, ou quelque
chose comme une imprimante.
Vous pouvez éviter les collisions en faisant des méthodes synchronisées.
synchronized void f();
synchronized void g();
Lorsque vous appelez une méthode synchronisée, cet objet est verrouillé et aucune autre
méthode synchronisée de cet objet ne peut être appelée jusqu'à ce que la première se termine
et libère le verrou. Si f() est appelée pour un objet alors f() ne peut pas être appelé par le
même objet jusqu'à ce que f() soit terminée.
Il y n'a qu'un seul verrou par classe, de sorte qu'une méthode statique synchronisée peut
empêcher toutes les autres méthodes d'un accès simultané.
ENSICAEN - Spécialité Informatique
Les méthodes writeExternal() et readExternal() de l'interface Externalizable sont
implémentées par une classe pour donner à la classe le contrôle sur le format et le contenu
du flux d'un objet et de ses supertypes.
24 ■ Java avancé
10.1.2 Section critique
Pour se prémunir contre l'accès simultané à une méthode de plusieurs threads, il faut
utiliser le mot clé synchronized à l’intérieur de la méthode :
public void method() {
synchronized(object) {
}
}
Un bloc synchronisé doit avoir un objet sur lequel se synchroniser, et très souvent, on
prend tout simplement l'objet lui-même : synchronized(this).
10.2 Variable volatile
Le mot clé volatile devant une variable ou un attribut empêche le compilateur
d'effectuer des optimisations telles que déplacer la variable de la pile vers le tas. Il faut
utiliser les variables volatiles quand elle est modifiée ou testée dans des threads distincts.
10.3 Création et exécution de thread
ENSICAEN - Spécialité Informatique
La façon la plus simple de créer un thread est d'hériter de la classe java.lang.Thread
qui a tout le code nécessaire pour créer et exécuter des threads. La méthode la plus
importante est run() qui doit être redéfinie. Chaque instance de thread est exécutée en
utilisant la méthode start().
public class SimpleThread extends Thread {
private volatile int countDown = 5;
private static int threadCount = 0;
private int threadNumber = ++threadCount;
public SimpleThread() {
System.out.println("Making " + threadNumber);
}
public void run() {
while(true) {
System.out.println("Thread " +
threadNumber + "(" + countDown + "#004488">")");
if (--countDown == 0) {
return;
}
}
}
public static void main( String[] args ) {
for(int i = 0; i < 5; i++) {
new SimpleThread().start();
}
System.out.println("All Threads Started");
}
}
Si une classe hérite déjà d'une autre classe on peut implémenter l'interface Runnable.
public class SimpleThread implement Runnable {
public SimpleThread() {
..
}
public void run() {
...
}
public static void main( String[] args ) {
Java avancé ■ 25
…
new Thread(new SimpleThread(), “name”).start();
}
}
Lorsque l'on crée et exécute un thread, même sans garder de référence sur cet objet, le
ramasse-miette ne peut pas le nettoyer. C'est parce qu'un thread garde une référence vers
lui-même lors de l'exécution. Cependant, après son exécution, le ramasse-miette peut
nettoyer.
10.4 Thread de type démon
Un thread de type démon est un thread qui est supposé fournir un service général en
tâche de fond tant que le programme fonctionne, mais qu'il ne fait pas partie de l'essence
même du programme. Pour définir un thread de type démon, il faut appeler la méthode
isDaemon(true) avant de lancer le thread. Tous les threads créés par la suite par un thread
démons sont des démons eux-mêmes.
10.5 Thread states
Un thread peut être dans l'un des quatre états suivants :
► exécutable ;
► mort ;
► bloqué.
10.5.1 Arrêter un thread
La façon normale pour tuer un thread est de stopper la méthode run(). Il faut pour cela
utiliser un booléen qui indique quand terminer la méthode run(). Très souvent, le booléen
doit être de type volatile.
10.5.2 Yielding
La méthode yield() permet de donner au thread un indice sur le fait que le thread en a
fait assez et que d'autres threads peuvent avoir le CPU. Il devient moins prioritaire.
public void run() {
while (true) {
System.out.println(this);
if (--coundown == 0 ) return;
yield();
}
}
Toutefois, yield() n'est utile que dans de rares situations où l'on souhaite faire des
réglages fins pour optimiser la vitesse d'exécution d'une application.
10.5.3 Sleeping
La méthode sleep() cesse l'exécution d'un thread pendant un temps déterminé donné
de millisecondes. En fait, la seule garantie est que le thread va s'arrêter au moins le laps de
temps spécifié, mais il peut prendre plus de temps avant de reprendre son exécution parce
que l'ordonnanceur de threads prend du temps supplémentaire pour le relancer.
ENSICAEN - Spécialité Informatique
► nouveau ;
26 ■ Java avancé
La méthode doit être placée à l'intérieur d'un try-catch car il est possible que sleep()
soit interrompue avant son expiration. Cela peut arriver si quelqu'un d'autre a une référence
au thread et appelle interrupt().
En général, il est préférable d'utiliser wait() lorsque l'on veut sortir d'un thread
suspendu à l'aide interrupt().
10.5.4 Priority
La méthode setPriority() est utilisée pour affecter une priorité à un threat. Bien que le
JDK définisse 10 niveaux de priorités, cela n'est pas garantie pour tous les systèmes
d''exploitation. La seule approche est de se contenter des trois niveaux MAX_PRIORITY,
NORM_PRIORITY et MIN_PRIORITY pour définir les niveaux de priorité.
10.5.5 Jonction de thread
Un thread peut appeler la méthode join() sur un autre thread pour attendre la fin du
second thread avant de s'exécuter et ainsi se joindre à lui après son exécution. Le lien peut
être suspendu en appelant interrupt() sur le thread appelant.
ENSICAEN - Spécialité Informatique
10.5.6 Waiting
Il est important de comprendre que sleep() ne libère pas le verrou lorsqu'il est appelé.
Par contre, la méthode wait() libère le verrou, ce qui signifie que d'autres méthodes
synchronisées de l'objet thread peut être appelé pendant un wait(). Un wait() continue
indéfiniment jusqu'à ce que le thread reçoive un notify() ou notifyAll().
Toutes ces méthodes comme sleep() font partie de la classe de base Object et pas de la
classe Thread. C'est parce qu'ils manipulent le verrou qui fait également partie de tous les
objets.
Quand un objet se notifie lui-même, on doit le faire à l'intérieur d'un bloc synchronisé
qui acquiert le verrou pour l'objet :
synchronized(x) {
x.notify();
}
La méthode sleep() peut être appelée à l'intérieur d'une méthode non synchronisée,
puisqu'elle ne manipule pas de verrou.
10.5.7 Blocage : le problème du dîner des philosophes (E. Dijktra)
// A dining philosopher
import java.util.concurrent.*;
import java.util.*;
import static net.mindview.util.Print.*;
public class Philosopher implements Runnable {
private Chopstick _left;
private Chopstick _right;
private final int _id;
Daemon _thread
private final int _ponderFactor;
private Random _rand = new Random(47);
private void pause() throws InterruptedException {
Java avancé ■ 27
if(_ponderFactor == 0) {
return;
}
TimeUnit.MILLISECONDS.sleep(rand.nextInt(ponderFactor * 250));
}
public void stopRequested() {
_stopRequested = true;
}
}
}
// Chopsticks for dining philosophers.
public class Chopstick {
private boolean _taken = false;
public synchronized void take() throws InterruptedException {
while (_taken) {
wait();
}
_taken = true;
}
public synchronized void drop() {
_taken = false;
notifyAll();
}
}
public class DeadlockingDiningPhilosophers31 {
ENSICAEN - Spécialité Informatique
public Philosopher(Chopstick left, Chopstick right, int ident, int
ponder) {
_left = left;
_right = right;
_id = ident;
_ponderFactor = ponder;
}
public void run() {
try {
while(!Thread.interrupted()) {
print(this + " " + "thinking");
pause();
// Philosopher becomes hungry
print(this + " " + "grabbing right");
_right.take();
print(this + " " + "grabbing left");
_left.take();
print(this + " " + "eating");
pause();
_right.drop();
_left.drop();
}
} catch (InterruptedException e) {
print(this + " " + "exiting via interrupt");
}
}
public String toString() {
return "Philosopher " + _id;
}
28 ■ Java avancé
ENSICAEN - Spécialité Informatique
public static void main(String[] args) throws Exception {
int ponder = 5;
if(args.length > 0)
ponder = Integer.parseInt(args[0]);
int size = 5;
if(args.length > 1)
size = Integer.parseInt(args[1]);
ExecutorService exec = Executors.newCachedThreadPool();
// chopstick bin:
LinkedBlockingQueue<Chopstick> bin = new
LinkedBlockingQueue<Chopstick>();
Chopstick[] sticks = new Chopstick[size];
for(int i = 0; i < size; i++) {
sticks[i] = new Chopstick();
bin.put(sticks[i]);
}
for(int i = 0; i < size; i++)
exec.execute(new Philosopher31(sticks[i], sticks[(i + 1) % size],
bin, i, ponder));
if(args.length == 3 && args[2].equals("timeout")) {
TimeUnit.SECONDS.sleep(5);
} else {
System.out.println("Press 'Enter' to quit");
System.in.read();
}
exec.shutdownNow();
}
}
10.6 Utiliser les pipes entre threads
Il faut pour cela utiliser les classes PipeWriter et PipeReader.
11. Le clonage d'objet
11.1 Copie profonde via le clonage
Par défaut la méthode clone() de la classe Object ne permet qu'une copie superficielle.
Cependant, même si la méthode de clonage est définie pour toute classe, le clonage n'est pas
automatiquement disponible. Pour que le clonage soit utilisable, il faut ajouter du code
spécifique.
En effet, la méthode clone () est protégée dans la classe de base Object. Non
seulement cela signifie qu'elle n'est pas disponible par défaut pour le client qui utilise la
classe, mais cela signifie aussi que vous ne pouvez pas appeler clone() par une référence à
la classe de base. Ainsi, les deux lignes suivantes produisent une erreur de compilation  :
Integer x = new Integer(1)
x = x.clone();
Cependant, une classe dérivée peut appeler la méthode clone() de la classe de base. Elle
fournit une duplication bit à bit de l'objet de la classe dérivée. Il faut toute fois rendre la
méthode publique pour qu'elle soit accessible. Mais, pour que cela soit possible, il faut
obligatoirement implémenter l'interface Cloneable. Cette interface est vide, il s'agit d'une
« tagging interface ».
public interface Cloneable { }
Le compilateur vérifie que la classe implémente l'interface Cloneable. Si non, il lève une
Java avancé ■ 29
exception CloneNotSupportedException dès que l'on accède à la méthode clone().
11.2 Copie profonde via la sérialisation
Quand on regarde la sérialisation de Java, on peut observer que l'objet qui est sérialisé
puis désérialisé est, en effet, cloné. Cependant, le clonage est environ quatre fois plus rapide
que la sérialisation.
12. Swing
12.1 Préférences
La classe java.pref.Preferences est utilisée pour stocker et récupérer des paires de
valeurs persistantes (des données qui restent sur le disque entre deux exécutions du
programme).
Le stockage de ces préférences est fait par le système d''exploitation et dépend donc de
celui-ci (sur Windows dans les registres, sur Linux dans les dossiers de préférences, etc),
mais cela est fait de façon transparente pour l'utilisateur.
prefs = Preferences.userNodeForPackage(this.getClass());
prefs.put(key,s)
prefs.putBoolean(key,b);
prefs.putInt(key,i);
prefs.putLong(key,l);
prefs.putDouble(key,d);
prefs.putFloat(key,f);
prefs.putByteArray(key,ba);
b= prefs.getBoolean(key,bdef);
i= prefs.getBoolean(key,idef);
l= prefs.getBoolean(key,ldef);
d= prefs.getBoolean(key,ddef);
f= prefs.getBoolean(key,fdef);
b= prefs.getBoolean(key,badef);
prefs.clear();
allkeys = prefs.keys();
prefs.remove(key);
12.2 Internationalisation et localisation
L'internationalisation est le processus qui consiste à définir une application de telle
façon qu'elle peut être adaptée à différentes langues et régions.
La localisation est le processus qui consiste à adapter un logiciel à une région ou une
langue spécifique en ajoutant des composants spécifiques et en traduisant les textes.
12.2.1 Internationalisation
Localisation : les éléments textuels tels que les messages et les labels des composants
graphiques ne doivent pas être codés en dur dans le programme. À la place, ils doivent être
stockés dans un fichier de ressource et chargés dynamiquement.
String language =”fr”;
String country =”Fr”;
Locale CurrentLocale = new Locale(language, country);
ResourceBundled messages = ResourceBundled(.getBundle(“messageBundle”,
currentLocal);
String message = messages.getString(“key”);
ENSICAEN - Spécialité Informatique
Pour stocker et récupérer les valeurs :
30 ■ Java avancé
Ce qui est traduisible :
► messages
► sons
► couleurs
► graphismes
► nombres
► monnaie
► unité de mesure
► date
► heure
► mise en page
Ce qui n'est pas traduisible :
► Message composite : “At 1:15 PM on April 13, 1998, we attack the 7 ships on Mars”.
ENSICAEN - Spécialité Informatique
1/ Dans le fichier de ressource : MessageBundle_en_US.properties
template=”At {2, time, short} on {2,date,Long} we attack the {1, number,
integer} ships on planet {0}.”
// {2, time, short}: the time portion. Short specify DateFormat.SHORT
format style
// {2, date, long}: the date portion. The same Date object is used for
both the time and the date variables. In the Object array of message
arguments the index of the element holding the Date object is 2.
// {1, number, integer}: an integer object. In the Object array of
message arguments the index of the element holding the integer object is
1.
// {0} the string in the ResourceBundled that corresponds to the “planet”
key.
2/ Dans le code :
// Message Arguments
Object[] messageArguments = {messages.getString(“Planet”),
new Integer(7),
new Date() };
// Create formatter
MessageFormat formatter = new MessageFormat(“”);
formatter.setLocale(currentLocale);
// format it
formatter.applyPattern(message.getString(“template”));
String output = formatter.format(messageArgument);
► Pluriel : “There is one file on XDisk”, “There are 2 files on Xdisk”.
1/ Dans le fichier de ressource : MessageBundle_en_US.properties
pattern=”There {0} on Xdisk.” // 0 : refers to the followings strings.
noFile= is no file
onFile= is one file
multipleFiles=The are {1} files // 1 refers to the number
2/ Dans le code :
Double[] fileLimits= {0, 1, 2};
String[] fileStrings = {
Java avancé ■ 31
bundle.getString(“noFile”);
bundle.getString(“oneFile”);
bundle.getString(“multipleFiles”);
};
ChoiceFormat choiceFormat = new ChoiceFormat(fileLimits, fileStrings);
Format[] formats = { choiceFormat, null, NumberFormat.getInstances()};
MessageFormat formatter = new MessageFormat(“”);
messageFormat applyPattern(“pattern”);
messageFormat setFormats(formats);
Object[] messageArguments = { null, null };
for (int numFiles =0; numFiles < 4; numFiles++ ) {
messageArguments[0] =new Integer(numFiles);
messageArguemnts[1] = new Integer(nulFiles);
String result = messageFormat.format(messagerArguments);
}
► Nombres
NumberFormat numberFormat =
NumberFormat.getNumberInstance(currentLOcale));
String value = numberFormat.format(new Double(2355544.23536));
► Pourcentage
► Monnaie
NumberFormat numberFormat =
NumberFormat.getCuurencyInstance(currentLOcale));
String value = numberFormat.format(new Double(78776,50));
► Date et heure
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.DEFAULT,
currentLocale);
TimeFormat timeFormat = TimeFormat.getTimeInstance(DateFormat.DEFAULT,
currentLocale);
DateFormat dateTimeFormat =
DateFormat.getDateTimeInstance(DateFormat.Long, DateFormat.Long,
currentLocale);
12.3 Unicode
Les valeurs de caractères Unicode sont accessibles avec :
String eWithCircumflex = new String(“\u00EA”);
Mauvaise utilisation de caractères :
if ((ch >='a' && ch <='z') ..
Bonne utilisation :
if (Character.isLetter(ch)) … Character.getType(), isLowerCase()...
12.4 Changer le texte des composants Swing
Par exemple, on peut changer les textes des boutons d'un JOptionPane pour les
franciser :
UIManager.put("OptionPane.yesButtonText", "Oui");
UIManager.put("OptionPane.cancelButtonText","Annuler");
UIManager.put("OptionPane.noButtonText", "No");
ENSICAEN - Spécialité Informatique
NumberFormat numberFormat =
NumberFormat.getPercentInstance(currentLOcale));
String value = numberFormat.format(new Double(0.75));
32 ■ Java avancé
UIManager.put("OptionPane.okButtonText", "OK");
12.5 Récupérer les noms des éléments d'un composant Swing
Cette classe affiche les noms des éléments d'un composant Swing, pour ensuite les
changer à souhait :
ENSICAEN - Spécialité Informatique
public class UIDefaults {
public static void main(String[] args) {
try {
UIManager.getLookAndFeelDefaults();
Set<Entry<Object, Object>> defaults =
UIManager.getDefaults().entrySet();
// this TreeSet will hold the sorted properties
TreeSet<Entry<Object, Object>> ts = new TreeSet<Entry<Object,
Object>>(
new Comparator<Map.Entry<Object, Object>>() {
@Override
public int compare(Map.Entry<Object, Object> a,
Map.Entry<Object, Object> b) {
Object o = a.getKey();
if (o instanceof String) {
return ((String)
a.getKey()).compareTo(((String)b.getKey()));
} else {
String sa = ""+a.getKey();
String sb = ""+a.getKey();
return sa.compareTo(sb);
}
}
});
ts.addAll(defaults);
for (Iterator<Entry<Object, Object>> i = ts.iterator();
i.hasNext();) {
Map.Entry<Object, Object> entry = i.next();
System.out.print(entry.getKey() + " = " );
System.out.println(entry.getValue());
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Voici un extrait :
ColorChooser.background
=
javax.swing.plaf.ColorUIResource[r=204,g=204,b=204]
ColorChooser.cancelText
=Cancel
ColorChooser.hsbBlueText
=B
ColorChooser.hsbBrightnessText =B
ColorChooser.hsbGreenText
=G
ColorChooser.hsbHueText
=H
ColorChooser.hsbNameText
=HSB
ColorChooser.hsbRedText
=R
ColorChooser.hsbSaturationText =S
ColorChooser.okText
=OK
ColorChooser.previewText
=Preview
ColorChooser.resetText
=Reset
ColorChooser.rgbBlueMnemonic
=66
ColorChooser.rgbBlueText
=Blue
Java avancé ■ 33
ColorChooser.rgbGreenMnemonic
=71
ColorChooser.rgbGreenText
=Green
ColorChooser.rgbNameText
=RGB
ColorChooser.rgbRedMnemonic
=82
ColorChooser.rgbRedText
=Red
ColorChooser.sampleText
=Sample Text
ColorChooser.swatchesDefaultRecentColor =
javax.swing.plaf.ColorUIResource[r=204,g=204,b=204]
ColorChooser.swatchesNameText =Swatches
ColorChooser.swatchesRecentSwatchSize =
java.awt.Dimension[width=10,height=10]
ColorChooser.swatchesRecentText
=Recent:
ColorChooser.swatchesSwatchSize
=
java.awt.Dimension[width=10,height=10]
FileChooser.acceptAllFileFilterText
=All Files (*.*)
FileChooser.cancelButtonMnemonic
=67
FileChooser.cancelButtonText
=Cancel
FileChooser.cancelButtonToolTipText
=Abort file chooser dialog
FileChooser.detailsViewButtonAccessibleName =Details
FileChooser.detailsViewButtonToolTipText
=Details
ENSICAEN - Spécialité Informatique
34 ■ Java avancé
Révision
Version
0.1
0.2
0.3
Date
Commentaire
Compiled texts from:
•
Bruce Eckel, “Thinking in Java”,
Feb 27, 2011
http://www.bruceeckel.com.
•
Matthias Ettrich, “designing QT-Style C++ APIS”.
•
http://doc.trolltech.com/qq/qq13-apis.html, 04/02/2011.
01/17/13 Révision complète.
11/02/14 Traduction en français.
ENSICAEN - Spécialité Informatique