 |
 |
8) Interfaces et classes internes |
|
 |
|
Texte original |
 |
Traducteur :
Jérome Quelin |
|
 |
///
|
Ce chapitre contient
6 pages
1
2
3
4
5
6
|
|
|
 |
 |
 |
 |
 |
 |
|
 |
|
 |
 |
 |
The rest of the code works the same. It
doesn’t matter if you are upcasting to a
“regular” class called Instrument, an abstract class
called Instrument, or to an interface
called Instrument. The behavior is the same. In fact, you can see in the
tune( ) method that there isn’t any evidence about whether
Instrument is a “regular” class, an abstract class, or
an interface. This is the intent: Each approach gives the programmer
different control over the way objects are created and
used.
|
 |
Le reste du code ne change pas. Cela ne gêne pas si on transtype vers
une classe « normale » appelée Instrument, une classe
abstract appelée Instrument, ou
une interface appelée Instrument. Le comportement reste le
même. En fait, on peut voir dans la méthode tune() qu'on ne peut savoir si
Instrument est une classe « normale », une classe
abstract, ou une interface. Et c'est bien le but recherché :
chaque approche donne au programmeur un contrôle différent sur la manière dont les objets sont
créés et utilisés.
|
 |
 |
 |
“Multiple inheritance” in Java
|
 |
« Héritage multiple » en Java
|
 |
 |
 |
The interface isn’t simply a
“more pure” form of abstract class. It has a higher purpose
than that. Because an interface has no implementation at all—that
is, there is no storage associated with an interface—there’s
nothing to prevent many interfaces from being combined. This is valuable
because there are times when you need to say “An x is an a
and a b and a c.” In C++, this act of
combining multiple class interfaces is called
multiple inheritance, and
it carries some rather sticky baggage because each class can have an
implementation. In Java, you can perform the same act, but only one of the
classes can have an implementation, so the problems seen in C++ do not occur
with Java when combining multiple interfaces:
|
 |
Une interface n'est pas simplement une forme « plus
pure » d'une classe abstract. Elle a un but plus important que cela.
Puisqu'une interface ne dispose d'aucune implémentation - autrement dit, aucun
stockage n'est associé à une interface -, rien n'empêche de combiner plusieurs
interfaces. Ceci est intéressant car certaines fois on a la relation "Un
x est un a et un b et un
c". En C++, le fait de combiner les interfaces de plusieurs classes est
appelé héritage multiple, et entraîne une lourde charge du fait que chaque classe
peut avoir sa propre implémentation. En Java, on peut réaliser la même chose, mais une seule classe
peut avoir une implémentation, donc les problèmes rencontrés en C++ n'apparaissent pas en Java
lorsqu'on combine les interfaces multiples :
|
 |
 |
 |
|
 |
|
 |
 |
 |
In a derived class, you aren’t
forced to have a base class that is either an abstract or
“concrete” (one with no abstract methods). If you do
inherit from a non-interface, you can inherit from only one. All
the rest of the base elements must be interfaces. You place all the
interface names after the implements keyword and separate them with
commas. You can have as many interfaces as you want—each one
becomes an independent type that you can upcast to. The following example shows
a concrete class combined with several interfaces to produce a new
class:
|
 |
Dans une classe dérivée, on n'est pas forcé d'avoir une classe de base qui
soit abstract ou « concrète » (i.e. sans méthode
abstract). Si une classe hérite d'une classe qui n'est pas une
interface, elle ne peut dériver que de cette seule classe. Tous les autres types
de base doivent être des interfaces. On place les noms des interfaces après le
mot-clef implements en les séparant par des virgules. On peut spécifier autant
d'interfaces qu'on veut - chacune devient un type indépendant vers lequel on peut
transtyper. L'exemple suivant montre une classe concrète combinée à plusieurs
interfaces pour produire une nouvelle classe :
|
 |
 |
 |
//: c08:Adventure.java
// Multiple interfaces.
import java.util.*;
interface CanFight {
void fight();
}
interface CanSwim {
void swim();
}
interface CanFly {
void fly();
}
class ActionCharacter {
public void fight() {}
}
class Hero extends ActionCharacter
implements CanFight, CanSwim, CanFly {
public void swim() {}
public void fly() {}
}
public class Adventure {
static void t(CanFight x) { x.fight(); }
static void u(CanSwim x) { x.swim(); }
static void v(CanFly x) { x.fly(); }
static void w(ActionCharacter x) { x.fight(); }
public static void main(String[] args) {
Hero h = new Hero();
t(h); // Treat it as a CanFight
u(h); // Treat it as a CanSwim
v(h); // Treat it as a CanFly
w(h); // Treat it as an ActionCharacter
}
} ///:~
|
 |
//: c08:Adventure.java // Interfaces multiples. import java.util.*;
interface CanFight { void fight(); }
interface CanSwim { void swim(); }
interface CanFly { void fly(); }
class ActionCharacter { public void fight() {} }
class Hero extends ActionCharacter implements CanFight, CanSwim, CanFly { public void swim() {} public void fly() {} }
public class Adventure { static void t(CanFight x) { x.fight(); } static void u(CanSwim x) { x.swim(); } static void v(CanFly x) { x.fly(); } static void w(ActionCharacter x) { x.fight(); } public static void main(String[] args) { Hero h = new Hero(); t(h); // Le traite comme un CanFight u(h); // Le traite comme un CanSwim v(h); // Le traite comme un CanFly w(h); // Le traite comme un ActionCharacter } } ///:~
|
 |
 |
 |
You can see that Hero combines the
concrete class ActionCharacter with the interfaces CanFight,
CanSwim, and CanFly. When you combine a concrete class with
interfaces this way, the concrete class must come first, then the interfaces.
(The compiler gives an error otherwise.)
|
 |
Ici, Hero combine la classe concrète
ActionCharacter avec les interfaces CanFight,
CanSwim et CanFly. Quand on combine une classe concrète avec des
interfaces de cette manière, la classe concrète doit être spécifiée en premier, avant les
interfaces (autrement le compilateur génère une erreur).
|
 |
 |
 |
Note that the signature for
fight( ) is the same in the interface CanFight and the class
ActionCharacter, and that fight( ) is not provided
with a definition in Hero. The rule for an interface is that you
can inherit from it (as you will see shortly), but then you’ve got another
interface. If you want to create an object of the new type, it must be a
class with all definitions provided. Even though Hero does not explicitly
provide a definition for fight( ), the definition comes along with
ActionCharacter so it is automatically provided and it’s possible
to create objects of Hero.
|
 |
Notons que la signature de fight() est la même dans
l'interface CanFight et dans la classe ActionCharacter, et que
Hero ne fournit pas de définition pour fight(). On peut
hériter d'une interface (comme on va le voir bientôt), mais dans ce cas on a une
autre interface. Si on veut créer un objet de ce nouveau type, ce doit être une
classe implémentant toutes les définitions. Bien que la classe Hero ne fournisse
pas explicitement une définition pour fight(), la définition est fournie par
ActionCharacter, donc héritée par Hero et il est ainsi possible
de créer des objets Hero.
|
 |
 |
 |
In class Adventure, you can see
that there are four methods that take as arguments the various interfaces and
the concrete class. When a Hero object is created, it can be passed to
any of these methods, which means it is being upcast to each interface in
turn. Because of the way interfaces are designed in Java, this works without a
hitch and without any particular effort on the part of the
programmer.
|
 |
Dans la classe Adventure, on peut voir quatre méthodes
prenant les diverses interfaces et la classe concrète en argument. Quand un objet
Hero est créé, il peut être utilisé dans chacune de ces méthodes, ce qui veut dire
qu'il est transtypé tour à tour dans chaque interface. De la façon dont cela est
conçu en Java, cela fonctionne sans problème et sans effort supplémentaire de la part du
programmeur.
|
 |
 |
 |
Keep in mind that the core reason for
interfaces is shown in the above example: to be able to upcast to more than one
base type. However, a second reason for using interfaces is the same as using an
abstract base class: to prevent the client programmer from making an
object of this class and to establish that it is only an interface. This brings
up a question: Should you use an
interface or an
abstract class? An interface gives you the benefits of an
abstract class and the benefits of an interface, so if
it’s possible to create your base class without any method definitions or
member variables you should always prefer interfaces to abstract
classes. In fact, if you know something is going to be a base class, your first
choice should be to make it an interface, and only if you’re forced
to have method definitions or member variables should you change to an
abstract class, or if necessary a concrete class.
|
 |
L'intérêt principal des interfaces est démontré dans l'exemple précédent :
être capable de transtyper vers plus d'un type de base. Cependant, une seconde raison, la même que
pour les classes de base abstract, plaide pour l'utilisation des interfaces :
empêcher le programmeur client de créer un objet de cette classe et spécifier qu'il ne s'agit que
d'une interface. Cela soulève une question : faut-il utiliser une interface
ou une classe abstract ? Une interface apporte les bénéfices
d'une classe abstract et les bénéfices d'une interface,
donc s'il est possible de créer la classe de base sans définir de méthodes ou de données membres,
il faut toujours préférer les interfaces aux classes abstract. En
fait, si on sait qu'un type sera amené à être dérivé, il faut le créer d'emblée comme une
interface, et ne le changer en classe abstract, voire en classe
concrète, que si on est forcé d'y placer des définitions de méthodes ou des données
membres.
|
 |
 |
 |
Name collisions when combining
interfaces
|
 |
Combinaison d'interfaces et collisions de noms
|
 |
 |
 |
You can encounter a small pitfall when
implementing multiple interfaces. In the above example, both CanFight and
ActionCharacter have an identical void fight( ) method. This
is no problem because the method is identical in both cases, but what if
it’s not? Here’s an example:
|
 |
On peut renconter un problème lorsqu'on implémente plusieurs interfaces. Dans
l'exemple précédent, CanFight et ActionCharacter ont tous les
deux une méthode void fight() identique. Cela ne pose pas de problèmes parce que
la méthode est identique dans les deux cas, mais que se passe-t-il lorsque ce n'est pas le cas ?
Voici un exemple :
|
 |
 |
 |
//: c08:InterfaceCollision.java
interface I1 { void f(); }
interface I2 { int f(int i); }
interface I3 { int f(); }
class C { public int f() { return 1; } }
class C2 implements I1, I2 {
public void f() {}
public int f(int i) { return 1; } // overloaded
}
class C3 extends C implements I2 {
public int f(int i) { return 1; } // overloaded
}
class C4 extends C implements I3 {
// Identical, no problem:
public int f() { return 1; }
}
// Methods differ only by return type:
//! class C5 extends C implements I1 {}
//! interface I4 extends I1, I3 {} ///:~
|
 |
//: c08:InterfaceCollision.java
interface I1 { void f(); } interface I2 { int f(int i); } interface I3 { int f(); } class C { public int f() { return 1; } }
class C2 implements I1, I2 { public void f() {} public int f(int i) { return 1; } // surchargée }
class C3 extends C implements I2 { public int f(int i) { return 1; } // surchargée }
class C4 extends C implements I3 { // Identique, pas de problème : public int f() { return 1; } }
// Les méthodes diffèrent seulement par le type de retour : //! class C5 extends C implements I1 {} //! interface I4 extends I1, I3 {} ///:~
|
 |
 |
 |
The difficulty occurs because overriding,
implementation, and overloading get unpleasantly mixed together, and overloaded
functions cannot differ only by return type. When the last two lines are
uncommented, the error messages say it all:
|
 |
Les difficultés surviennent parce que la redéfinition, l'implémentation et
la surcharge sont toutes les trois utilisées ensemble, et que les fonctions surchargées ne peuvent
différer seulement par leur type de retour. Quand les deux dernières lignes sont décommentées, le
message d'erreur est explicite :
|
 |
 |
 |
InterfaceCollision.java:23: f() in C cannot
implement f() in I1; attempting to use
incompatible return type
found : int
required: void
InterfaceCollision.java:24: interfaces I3 and I1 are incompatible; both define f
(), but with different return type
|
 |
InterfaceCollision.java:23: f() in C cannot implement f() in I1; attempting to use incompatible return type found : int required: void InterfaceCollision.java:24: interfaces I3 and I1 are incompatible; both define f (), but with different return type
|
 |
 |
 |
Using the same method names in different
interfaces that are intended to be combined generally causes confusion in the
readability of the code, as well. Strive to avoid
it.
|
 |
De toutes façons, utiliser les mêmes noms de méthode dans différentes
interfaces destinées à être combinées affecte la compréhension du code. Il faut donc l'éviter
autant que faire se peut.
|
 |
 |
 |
Extending an interface with inheritance
|
 |
Etendre une interface avec l'héritage
|
 |
 |
 |
You can easily add new method
declarations to an
interface using
inheritance, and you can also combine several interfaces into a new
interface with inheritance. In both cases you get a new interface,
as seen in this example:
|
 |
On peut facilement ajouter de nouvelles déclarations de méthodes à
une interface en la dérivant, de même qu'on peut combiner plusieurs
interfaces dans une nouvelle interface grâce à l'héritage. Dans
les deux cas on a une nouvelle interface, comme dans l'exemple suivant :
|
 |
 |
 |
//: c08:HorrorShow.java
// Extending an interface with inheritance.
interface Monster {
void menace();
}
interface DangerousMonster extends Monster {
void destroy();
}
interface Lethal {
void kill();
}
class DragonZilla implements DangerousMonster {
public void menace() {}
public void destroy() {}
}
interface Vampire
extends DangerousMonster, Lethal {
void drinkBlood();
}
class HorrorShow {
static void u(Monster b) { b.menace(); }
static void v(DangerousMonster d) {
d.menace();
d.destroy();
}
public static void main(String[] args) {
DragonZilla if2 = new DragonZilla();
u(if2);
v(if2);
}
} ///:~
|
 |
//: c08:HorrorShow.java // Extension d'une interface grâce à l'héritage.
interface Monster { void menace(); }
interface DangerousMonster extends Monster { void destroy(); }
interface Lethal { void kill(); }
class DragonZilla implements DangerousMonster { public void menace() {} public void destroy() {} }
interface Vampire extends DangerousMonster, Lethal { void drinkBlood(); }
class HorrorShow { static void u(Monster b) { b.menace(); } static void v(DangerousMonster d) { d.menace(); d.destroy(); } public static void main(String[] args) { DragonZilla if2 = new DragonZilla(); u(if2); v(if2); } } ///:~
|
 |
 |
 |
DangerousMonster is a simple
extension to Monster that produces a new interface. This is
implemented in DragonZilla.
|
 |
DangerousMonster est une simple extension de
Monster qui fournit une nouvelle interface. Elle est implémentée
dans DragonZilla.
|
 |
 |
 |
The syntax used in Vampire works
only when inheriting interfaces. Normally, you can use
extends with only a single class, but since an
interface can be made from multiple other interfaces, extends can
refer to multiple base interfaces when building a new interface. As you
can see, the interface names are simply separated with
commas.
|
 |
La syntaxe utilisée dans Vampire n'est valide que
lorsqu'on dérive des interfaces. Normalement, on ne peut utiliser extends
qu'avec une seule classe, mais comme une interface peut être constituée de
plusieurs autres interfaces, extends peut se référer à plusieurs interfaces de
base lorsqu'on construit une nouvelle interface. Comme on peut le voir, les noms
d'interface sont simplement séparées par des virgules.
|
 |
 |
 |
Grouping constants
|
 |
Groupes de constantes
|
 |
 |
 |
Because any fields you put into an
interface are automatically static and final, the
interface is a convenient tool for
creating groups of constant
values, much as you would with an enum in C or C++. For
example:
|
 |
Puisque toutes les données membres d'une interface sont
automatiquement static et final, une interface
est un outil pratique pour créer des groupes de constantes, un peu comme avec le
enum du C ou du C++. Par exemple :
|
 |
 |
 |
//: c08:Months.java
// Using interfaces to create groups of constants.
package c08;
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;
} ///:~
|
 |
//: c08:Months.java // Utiliser les interfaces pour créer des groupes de constantes. package c08;
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; } ///:~
|
 |
 |
 |
Notice the Java style of using all
uppercase letters (with underscores to separate multiple words in a single
identifier) for static finals that have constant
initializers.
|
 |
Notons au passage l'utilisation des conventions de style Java pour les
champs static finals initialisés par des constantes : rien que
des majuscules (avec des underscores pour séparer les mots à l'intérieur d'un
identifiant).
|
 |
 |
 |
The fields in an interface are
automatically public, so it’s unnecessary to specify
that.
|
 |
Les données membres d'une interface sont automatiquement
public, il n'est donc pas nécessaire de le spécifier.
|
 |
 |
 |
Now you can use the constants from
outside the package by importing c08.* or c08.Months just as you
would with any other package, and referencing the values with expressions like
Months.JANUARY. Of course, what you get is just an int, so there
isn’t the extra type safety that C++’s enum has, but this
(commonly used) technique is certainly an improvement over hard-coding numbers
into your programs. (That approach is often referred to as using “magic
numbers” and it produces very difficult-to-maintain
code.)
|
 |
Maintenant on peut utiliser les constantes à l'extérieur du package en
important c08.* ou c08.Months de la même manière qu'on le ferait
avec n'importe quel autre package, et référencer les valeurs avec des expressions comme
Months.JANUARY. Bien sûr, on ne récupère qu'un int, il n'y a donc
pas de vérification additionnelle de type comme celle dont dispose l'enum du C++,
mais cette technique (couramment utilisée) reste tout de même une grosse amélioration comparée aux
nombres codés en dur dans les programmes (appelés « nombres magiques » et produisant un code
pour le moins difficile à maintenir).
|
 |
 |
 |
If you do want extra type safety, you can
build a class like
this[38]:
|
 |
Si on veut une vérification additionnelle de type, on peut construire une
classe de la manière suivante [38]:
|
 |
 |
 |
//: c08:Month2.java
// A more robust enumeration system.
package c08;
public final class Month2 {
private String name;
private Month2(String nm) { name = nm; }
public String toString() { return name; }
public final static Month2
JAN = new Month2("January"),
FEB = new Month2("February"),
MAR = new Month2("March"),
APR = new Month2("April"),
MAY = new Month2("May"),
JUN = new Month2("June"),
JUL = new Month2("July"),
AUG = new Month2("August"),
SEP = new Month2("September"),
OCT = new Month2("October"),
NOV = new Month2("November"),
DEC = new Month2("December");
public final static Month2[] month = {
JAN, JAN, FEB, MAR, APR, MAY, JUN,
JUL, AUG, SEP, OCT, NOV, DEC
};
public static void main(String[] args) {
Month2 m = Month2.JAN;
System.out.println(m);
m = Month2.month[12];
System.out.println(m);
System.out.println(m == Month2.DEC);
System.out.println(m.equals(Month2.DEC));
}
} ///:~
|
 |
//: c08:Month2.java // Un système d'énumération plus robuste. package c08;
public final class Month2 { private String name; private Month2(String nm) { name = nm; } public String toString() { return name; } public final static Month2 JAN = new Month2("January"), FEB = new Month2("February"), MAR = new Month2("March"), APR = new Month2("April"), MAY = new Month2("May"), JUN = new Month2("June"), JUL = new Month2("July"), AUG = new Month2("August"), SEP = new Month2("September"), OCT = new Month2("October"), NOV = new Month2("November"), DEC = new Month2("December"); public final static Month2[] month = { JAN, JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC }; public static void main(String[] args) { Month2 m = Month2.JAN; System.out.println(m); m = Month2.month[12]; System.out.println(m); System.out.println(m == Month2.DEC); System.out.println(m.equals(Month2.DEC)); } } ///:~
|
 |
 |
 |
The class is called Month2, since
there’s already a Month in the standard Java library. It’s a
final class with a private constructor so no one can inherit from
it or make any instances of it. The only instances are the final static
ones created in the class itself: JAN, FEB, MAR, etc. These
objects are also used in the array month, which lets you choose months by
number instead of by name. (Notice the extra JAN in the array to provide
an offset by one, so that December is month 12.) In main( ) you can
see the type safety: m is a Month2 object
so it can be assigned only to a Month2. The previous example
Months.java provided only int values, so an int variable
intended to represent a month could actually be given any integer value, which
wasn’t very safe.
|
 |
Cette classe est appelée Month2, puisque
Month existe déjà dans la bibliothèque Java standard. C'est une classe
final avec un constructeur private afin que personne ne puisse la
dériver ou en faire une instance. Les seules instances sont celles static final
créées dans la classe elle-même : JAN, FEB, MAR,
etc. Ces objets sont aussi utilisés dans le tableau month, qui permet de choisir
les mois par leur index au lieu de leur nom (notez le premier JAN dans le tableau
pour introduire un déplacement supplémentaire de un, afin que Décembre soit le mois numéro 12).
Dans main() on dispose de la vérification additionnelle de type :
m est un objet Month2 et ne peut donc se voir assigné qu'un
Month2. L'exemple précédent (Months.java) ne fournissait que des
valeurs int, et donc une variable int destinée à représenter un
mois pouvait en fait recevoir n'importe quelle valeur entière, ce qui n'était pas très
sûr.
|
 |
 |
 |
This approach also allows you to use
== or equals( ) interchangeably, as shown at the end of
main( ).
|
 |
Cette approche nous permet aussi d'utiliser indifféremment
== ou equals(), ainsi que le montre la fin de
main().
|
 |
 |
 |
Initializing fields in
interfaces
|
 |
Initialisation des données membres des interfaces
|
 |
 |
 |
Fields defined in interfaces are
automatically static and final. These cannot be “blank
finals,” but they can be initialized with nonconstant expressions. For
example:
|
 |
Les champs définis dans les interfaces sont automatiquement
static et final. Ils ne peuvent être des « finals
vides », mais peuvent être initialisés avec des expressions non constantes. Par exemple
:
|
 |
 |
 |
//: c08:RandVals.java
// Initializing interface fields with
// non-constant initializers.
import java.util.*;
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;
} ///:~
|
 |
//: c08:RandVals.java // Initialisation de champs d'interface // avec des valeurs non-constantes. import java.util.*;
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; } ///:~
|
 |
 |
 |
Since the fields are static, they
are initialized when the class is first loaded, which happens when any of the
fields are accessed for the first time. Here’s a simple
test:
|
 |
Comme les champs sont static, ils sont initialisés quand
la classe est chargée pour la première fois, ce qui arrive quand n'importe lequel des champs est
accédé pour la première fois. Voici un simple test :
|
 |
 |
 |
//: c08:TestRandVals.java
public class TestRandVals {
public static void main(String[] args) {
System.out.println(RandVals.rint);
System.out.println(RandVals.rlong);
System.out.println(RandVals.rfloat);
System.out.println(RandVals.rdouble);
}
} ///:~
|
 |
//: c08:TestRandVals.java
public class TestRandVals { public static void main(String[] args) { System.out.println(RandVals.rint); System.out.println(RandVals.rlong); System.out.println(RandVals.rfloat); System.out.println(RandVals.rdouble); } } ///:~
|
 |
 |
 |
The fields, of course, are not part of
the interface but instead are stored in the static storage area for that
interface.
|
 |
Les données membres, bien sûr, ne font pas partie de l'interface mais sont
stockées dans la zone de stockage static de cette interface.
|
 |
 |
 |
Nesting interfaces
|
 |
Interfaces imbriquées
|
 |
 |
 |
[39]Interfaces
may be nested within classes and within other interfaces. This reveals a number
of very interesting features:
|
 |
[39]Les interfaces peuvent être imbriquées
dans des classes ou à l'intérieur d'autres interfaces. Ceci révèle nombre de fonctionnalités
intéressantes :
|
 |
 |
 |
//: c08:NestingInterfaces.java
class A {
interface B {
void f();
}
public class BImp implements B {
public void f() {}
}
private class BImp2 implements B {
public void f() {}
}
public interface C {
void f();
}
class CImp implements C {
public void f() {}
}
private class CImp2 implements C {
public void f() {}
}
private interface D {
void f();
}
private class DImp implements D {
public void f() {}
}
public class DImp2 implements D {
public void f() {}
}
public D getD() { return new DImp2(); }
private D dRef;
public void receiveD(D d) {
dRef = d;
dRef.f();
}
}
interface E {
interface G {
void f();
}
// Redundant "public":
public interface H {
void f();
}
void g();
// Cannot be private within an interface:
//! private interface I {}
}
public class NestingInterfaces {
public class BImp implements A.B {
public void f() {}
}
class CImp implements A.C {
public void f() {}
}
// Cannot implement a private interface except
// within that interface's defining class:
//! class DImp implements A.D {
//! public void f() {}
//! }
class EImp implements E {
public void g() {}
}
class EGImp implements E.G {
public void f() {}
}
class EImp2 implements E {
public void g() {}
class EG implements E.G {
public void f() {}
}
}
public static void main(String[] args) {
A a = new A();
// Can't access A.D:
//! A.D ad = a.getD();
// Doesn't return anything but A.D:
//! A.DImp2 di2 = a.getD();
// Cannot access a member of the interface:
//! a.getD().f();
// Only another A can do anything with getD():
A a2 = new A();
a2.receiveD(a.getD());
}
} ///:~
|
 |
//: c08:NestingInterfaces.java
class A { interface B { void f(); } public class BImp implements B { public void f() {} } private class BImp2 implements B { public void f() {} } public interface C { void f(); } class CImp implements C { public void f() {} } private class CImp2 implements C { public void f() {} } private interface D { void f(); } private class DImp implements D { public void f() {} } public class DImp2 implements D { public void f() {} } public D getD() { return new DImp2(); } private D dRef; public void receiveD(D d) { dRef = d; dRef.f(); } }
interface E { interface G { void f(); } // « public » est redondant : public interface H { void f(); } void g(); // Ne peut pas être private dans une interface : //! private interface I {} }
public class NestingInterfaces { public class BImp implements A.B { public void f() {} } class CImp implements A.C { public void f() {} } // Ne peut pas implémenter une interface private sauf // à l'intérieur de la classe définissant cette interface : //! class DImp implements A.D { //! public void f() {} //! } class EImp implements E { public void g() {} } class EGImp implements E.G { public void f() {} } class EImp2 implements E { public void g() {} class EG implements E.G { public void f() {} } } public static void main(String[] args) { A a = new A(); // Ne peut accéder à A.D : //! A.D ad = a.getD(); // Ne renvoie qu'un A.D : //! A.DImp2 di2 = a.getD(); // Ne peut accéder à un membre de l'interface : //! a.getD().f(); // Seul un autre A peut faire quelque chose avec getD() : A a2 = new A(); a2.receiveD(a.getD()); } } ///:~
|
 |
 |
 |
The syntax for nesting an interface
within a class is reasonably obvious, and just like non-nested interfaces these
can have public or “friendly” visibility. You can also see
that both public and “friendly” nested interfaces can be
implemented as a public, “friendly,” and private
nested classes.
|
 |
La syntaxe permettant d'imbriquer une interface à l'intérieur d'une classe
est relativement évidente ; et comme les interfaces non imbriquées, elles peuvent avoir une
visibilité public ou « amicale ». On peut aussi constater que les
interfaces public et « amicales » peuvent être implémentées dans des
classes imbriquées public, « amicales » ou
private.
|
 |
 |
 |
As a new twist,
interfaces can also be
private as seen in A.D (the same qualification syntax is used for
nested interfaces as for nested classes). What good is a private nested
interface? You might guess that it can only be implemented as a private
nested class as in DImp, but A.DImp2 shows that it can also be
implemented as a public class. However, A.DImp2 can only be used
as itself. You are not allowed to mention the fact that it implements the
private interface, so implementing a private interface is a way to
force the definition of the methods in that interface without adding any type
information (that is, without allowing any upcasting).
|
 |
Une nouvelle astuce consiste à rendre les interfaces
private comme A.D (la même syntaxe est utilisée pour la
qualification des interfaces imbriquées et pour les classes imbriquées). A quoi sert une interface
imbriquée private ? On pourrait penser qu'elle ne peut être implémentée que comme
une classe private imbriquée comme DImp, mais
A.DImp2 montre qu'elle peut aussi être implémentée dans une classe
public. Cependant, A.DImp2 ne peut être utilisée que comme
elle-même : on ne peut mentionner le fait qu'elle implémente l'interface private,
et donc implémenter une interface private est une manière de forcer la définition
des méthodes de cette interface sans ajouter aucune information de type (c'est à dire, sans
autoriser de transtypage ascendant).
|
 |
 |
 |
The method getD( ) produces a
further quandary concerning the private interface: it’s a
public method that returns a reference to a private interface.
What can you do with the return value of this method? In main( ),
you can see several attempts to use the return value, all of which fail. The
only thing that works is if the return value is handed to an object that has
permission to use it—in this case, another A, via the
received( ) method.
|
 |
La méthode getD() se trouve quant à elle dans une impasse
du fait de l'interface private : c'est une méthode public qui
renvoie une référence à une interface private. Que peut-on faire avec la valeur de
retour de cette méthode ? Dans main(), on peut voir plusieurs tentatives pour
utiliser cette valeur de retour, qui échouent toutes. La seule solution possible est lorsque la
valeur de retour est gérée par un objet qui a la permission de l'utiliser - dans ce cas, un objet
A, via la méthode receiveD().
|
 |
 |
 |
Interface E shows that interfaces
can be nested within each other. However, the rules about interfaces—in
particular, that all interface elements must be public—are strictly
enforced here, so an interface nested within another interface is automatically
public and cannot be made private.
|
 |
L'interface E montre que les interfaces peuvent être
imbriquées les unes dans les autres. Cependant, les règles portant sur les interfaces - en
particulier celle stipulant que tous les éléments doivent être public - sont
strictement appliquées, donc une interface imbriquée à l'intérieur d'une autre interface est
automatiquement public et ne peut être déclarée
private.
|
 |
 |
 |
NestingInterfaces shows the
various ways that nested interfaces can be implemented. In particular, notice
that when you implement an interface, you are not required to implement any
interfaces nested within. Also, private interfaces cannot be implemented
outside of their defining classes.
|
 |
NestingInterfaces montre les différentes manières dont les
interfaces imbriquées peuvent être implémentées. En particulier, il est bon de noter que lorsqu'on
implémente une interface, on n'est pas obligé d'en implémenter les interfaces imbriquées. De plus,
les interfaces private ne peuvent être implémentées en dehors de leur classe de
définition.
|
 |
 |
 |
Initially, these features may seem like they are added strictly for syntactic consistency, but I generally find that once you know about a feature, you often discover places where it is useful."_Toc481064649"> |
 |
On peut penser que ces fonctionnalités n'ont été introduites que pour
assurer une cohérence syntaxique, mais j'ai remarqué qu'une fois qu'une fonctionnalité est connue,
on découvre souvent des endroits où elle se révèle utile.
|
 |
 |
 |
Inner classes
|
 |
Classes internes
|
 |
 |
 |
It’s possible to place a class
definition within another class definition. This is called an inner
class. The inner class is a
valuable feature because it allows you to group classes that logically belong
together and to control the visibility of one within the other. However,
it’s important to understand that inner classes are distinctly different
from composition.
|
 |
Il est possible de placer la définition d'une classe à l'intérieur de la
définition d'une autre classe. C'est ce qu'on appelle une classe interne. Les classes internes
sont une fonctionnalité importante du langage car elles permettent de grouper les classes qui sont
logiquement rattachées entre elles, et de contrôler la visibilité de l'une à partir de l'autre.
Cependant, il est important de comprendre que le mécanisme des classes internes est complètement
différent de celui de la composition.
|
 |
 |
 |
Often, while you’re learning about
them, the need for inner classes isn’t immediately obvious. At the end of
this section, after all of the syntax and semantics of inner classes have been
described, you’ll find examples that should make clear the benefits of
inner classes.
|
 |
Souvent, lorsqu'on en entend parler pour la première fois, l'intérêt des
classes internes n'est pas immédiatement évident. A la fin de cette section, après avoir discuté de
la syntaxe et de la sémantique des classes internes, vous trouverez des exemples qui devraient
clairement montrer les bénéfices des classes internes.
|
 |
 |
 |
You create an inner class just as
you’d expect—by placing the class definition inside a surrounding
class:
|
 |
Une classe interne est créée comme on pouvait s'y attendre - en plaçant la
définition de la classe à l'intérieur d'une autre classe :
|
 |
 |
 |
//: c08:Parcel1.java
// Creating inner classes.
public class Parcel1 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
// Using inner classes looks just like
// using any other class, within Parcel1:
public void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel1 p = new Parcel1();
p.ship("Tanzania");
}
} ///:~
|
 |
//: c08:Parcel1.java // Création de classes internes.
public class Parcel1 { class Contents { private int i = 11; public int value() { return i; } } class Destination { private String label; Destination(String whereTo) { label = whereTo; } String readLabel() { return label; } } // L'utilisation d'une classe interne ressemble à // l'utilisation de n'importe quelle autre classe depuis Parcell : public void ship(String dest) { Contents c = new Contents(); Destination d = new Destination(dest); System.out.println(d.readLabel()); } public static void main(String[] args) { Parcel1 p = new Parcel1(); p.ship("Tanzania"); } } ///:~
|
 |
 |
 |
The inner classes, when used inside
ship( ), look just like the use of any other classes. Here, the only
practical difference is that the names are nested within Parcel1.
You’ll see in a while that this isn’t the only
difference.
|
 |
Les classes internes, quand elles sont utilisées dans
ship(), ressemblent à n'importe quelle autre classe. La seule différence en est
que les noms sont imbriqués dans Parcel1. Mais nous allons voir dans un moment que
ce n'est pas la seule différence.
|
 |
 |
 |
More typically, an outer class will have
a method that returns a reference to an inner class, like this:
|
 |
Plus généralement, une classe externe peut définir une méthode qui renvoie
une référence à une classe interne, comme ceci :
|
 |
 |
 |
//: c08:Parcel2.java
// Returning a reference to an inner class.
public class Parcel2 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
public Destination to(String s) {
return new Destination(s);
}
public Contents cont() {
return new Contents();
}
public void ship(String dest) {
Contents c = cont();
Destination d = to(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel2 p = new Parcel2();
p.ship("Tanzania");
Parcel2 q = new Parcel2();
// Defining references to inner classes:
Parcel2.Contents c = q.cont();
Parcel2.Destination d = q.to("Borneo");
}
} ///:~
|
 |
//: c08:Parcel2.java // Renvoyer une référence à une classe interne.
public class Parcel2 { class Contents { private int i = 11; public int value() { return i; } } class Destination { private String label; Destination(String whereTo) { label = whereTo; } String readLabel() { return label; } } public Destination to(String s) { return new Destination(s); } public Contents cont() { return new Contents(); } public void ship(String dest) { Contents c = cont(); Destination d = to(dest); System.out.println(d.readLabel()); } public static void main(String[] args) { Parcel2 p = new Parcel2(); p.ship("Tanzania"); Parcel2 q = new Parcel2(); // Définition de références sur des classes internes : Parcel2.Contents c = q.cont(); Parcel2.Destination d = q.to("Borneo"); } } ///:~
|
 |
 |
 |
If you want to make an object of the
inner class anywhere except from within a non-static method of the outer
class, you must specify the type of that object as
OuterClassName.InnerClassName, as seen in
main( ).
|
 |
Si on veut créer un objet de la classe interne ailleurs que dans une
méthode non-static de la classe externe, il faut spécifier le type de cet objet
comme NomDeClasseExterne.NomDeClasseInterne, comme on peut le voir dans
main().
|
 |
 |
 |
Inner classes and
upcasting
|
 |
Classes internes et transtypage ascendant
|
 |
 |
 |
So far, inner classes don’t seem
that dramatic. After all, if it’s hiding you’re after, Java already
has a perfectly good hiding mechanism—just allow the class to be
“friendly” (visible only within a
package) rather than creating it
as an inner class.
|
 |
Jusqu'à présent, les classes internes ne semblent pas tellement
intéressantes. Après tout, si le but recherché est le camouflage, Java propose déjà un très bon
mécanisme pour cela - il suffit de rendre la classe « amicale » (visible seulement depuis
un certain package) plutôt que de la déclarer comme une classe interne.
|
 |
 |
 |
However, inner
classes really come into their own when you start upcasting to a base class, and
in particular to an interface. (The effect of producing an interface
reference from an object that implements it is essentially the same as upcasting
to a base class.) That’s because the inner class—the implementation
of the interface—can then be completely unseen and unavailable to
anyone, which is convenient for hiding the implementation. All you get back is a
reference to the base class or the interface.
|
 |
Cependant, les classes internes prennent de l'intérêt lorsqu'on transtype
vers une classe de base, et en particulier vers une interface (produire une
référence vers une interface depuis un objet l'implémentant revient à transtyper vers une classe de
base). En effet la classe interne - l'implémentation de l'interface - est
complètement masquée et indisponible pour tout le monde, ce qui est pratique pour cacher
l'implémentation. La seule chose qu'on récupère est une référence sur la classe de base ou
l'interface.
|
 |
 |
 |
First, the common interfaces will be
defined in their own files so they can be used in all the
examples:
|
 |
Tout d'abord, les interfaces sont définies dans leurs propres fichiers afin
de pouvoir être utilisées dans tous les exemples :
|
 |
 |
 |
//: c08:Destination.java
public interface Destination {
String readLabel();
} ///:~
|
 |
//: c08:Destination.java public interface Destination { String readLabel(); } ///:~
|
 |
 |
 |
//: c08:Contents.java
public interface Contents {
int value();
} ///:~
|
 |
//: c08:Contents.java public interface Contents { int value(); } ///:~
|
 |
 |
 |
Now Contents and
Destination represent interfaces available to the client programmer. (The
interface, remember, automatically makes all of its members
public.)
|
 |
Maintenant Contents et Destination sont
des interfaces disponibles pour le programmeur client (une interface déclare
automatiquement tous ses membres comme public).
|
 |
 |
 |
When you get back a reference to the base
class or the interface, it’s possible that you can’t even
find out the exact type, as shown here:
|
 |
Quand on récupère une référence sur la classe de base ou
l'interface, il est possible qu'on ne puisse même pas en découvrir le type exact,
comme on peut le voir dans le code suivant :
|
 |
 |
 |
//: c08:Parcel3.java
// Returning a reference to an inner class.
public class Parcel3 {
private class PContents implements Contents {
private int i = 11;
public int value() { return i; }
}
protected class PDestination
implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
public Destination dest(String s) {
return new PDestination(s);
}
public Contents cont() {
return new PContents();
}
}
class Test {
public static void main(String[] args) {
Parcel3 p = new Parcel3();
Contents c = p.cont();
Destination d = p.dest("Tanzania");
// Illegal -- can't access private class:
//! Parcel3.PContents pc = p.new PContents();
}
} ///:~
|
 |
//: c08:Parcel3.java // Renvoyer une référence sur une classe interne.
public class Parcel3 { private class PContents implements Contents { private int i = 11; public int value() { return i; } } protected class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo; } public String readLabel() { return label; } } public Destination dest(String s) { return new PDestination(s); } public Contents cont() { return new PContents(); } }
class Test { public static void main(String[] args) { Parcel3 p = new Parcel3(); Contents c = p.cont(); Destination d = p.dest("Tanzania"); // Illégal -- ne peut accéder à une classe private : //! Parcel3.PContents pc = p.new PContents(); } } ///:~
|
 |
 |
 |
Note that since main( ) is in
Test, when you want to run this program you don’t execute
Parcel3, but instead:
|
 |
Notez que puisque main() se trouve dans
Test, pour lancer ce programme il ne faut pas exécuter Parcel3,
mais :
|
 |
 |
 |
java Test
|
 |
java Test
|
 |
 |
 |
 |
 |
 |
 |
 |
|
 |
 |
 |