/**
POGL : programmation objet et génie logiciel
Classes internes
@author Thibaut Balabonski @ Université Paris-Sud
@version 4 février 2019
*/
/**
En plus des habituels attributs, constructeurs et méthodes, une classe peut
contenir les définitions d'autres classes, appelées classes internes.
Ceci permet notamment de définir des structures internes à une classe
donnée.
Par exemple, nous pouvons définir une liste chaînée comme étant constituée
d'un ensemble de blocs. La classe [Block] sera naturellement une classe
interne de la classe [List].
*/
class IntList {
// On donne comme attribut à la liste un pointeur vers son premier bloc,
// du type [IntBlock] défini juste après par une classe interne.
private IntBlock firstBlock;
// Définition de classe interne, avec exactement la syntaxe habituelle
class IntBlock {
// On a comme d'habitude des attributs, des constructeurs, des méthodes
private IntBlock nextBlock;
private final Integer elt;
public IntBlock(int n) {
this.elt = n;
}
public int getElt() {
return this.elt;
}
}
// Pour ajouter un élément au début de la liste
public void addHead(int n) {
// on crée d'abord un nouveau bloc [b]
IntBlock b = new IntBlock(n);
// puis on ajuste les pointeurs : [b] devient le nouveau premier
// bloc, et son successeur est l'ancien premier bloc
b.nextBlock = this.firstBlock;
this.firstBlock = b;
}
// Récupérer le premier bloc
// On restreint la visibilité au paquet, car ce genre de structure interne
// n'a pas vocation à être montré à l'extérieur
protected IntBlock getFirstBlock() {
return this.firstBlock;
}
}
/**
Visibilité de la classe interne depuis la classe externe
Une classe externe a accès à tous les champs de ses classes internes,
y compris les champs privés.
Ainsi dans le code précédent nous avons pu écrire
b.nextBlock = ...
Dans une méthode de [IntList].
*/
/**
Visibilité de la classe interne depuis l'extérieur
Si une classe interne est déclarée publique, ou par défaut si rien n'est
précisé, elle est connue du monde extérieur sous un nom construit selon le
schéma suivant :
NomClasseExterne . NomClasseInterne
De plus, toute classe extérieur peut alors aussi faire référence aux champs
de la classe interne.
Si la classe interne est déclarée privée, ceci n'est pas possible.
On peut donc faire référence aux blocs de notre liste depuis une classe
extérieure sous le nom de classe
IntList.IntBlock
*/
class TestIntList {
public static void main(String[] args) {
IntList list = new IntList();
list.addHead(1);
list.addHead(2);
list.addHead(4);
// Vus de l'extérieurs les blocs ont pour type [IntList.IntBlock].
// Les règles de visibilité des champs publics, protégés ou privés
// sont les mêmes que d'habitude
IntList.IntBlock b = list.getFirstBlock();
// IntList.IntBlock c = b.nextBlock; // Impossible, [nextBlock] privé
System.out.println(b.getElt());
}
}
/**
Création d'instances
Une instance de la classe interne ne peut être construite que par
l'intermédiaire d'une instance de la classe externe. Cela peut se faire
de deux façons :
- soit au sein d'une méthode d'instance de la classe externe (c'est-à-dire
une méthode dans laquelle [this] existe et désigne une instance de la
classe externe) ; c'est notamment ce qui se passe dans la ligne
IntBlock b = new IntBlock(n);
de la méthode [addHead],
- soit en faisant explicitement référence à une instance [extObj] de la
classe externe, en remplaçant [new] par
extObj . new
Ainsi, chaque instance de la classe interne est liée à une instance de la
classe externe (celle qui a servi à sa création).
*/
class TestInternalConstructor {
public static void main(String[] args) {
IntList list = new IntList();
IntList.IntBlock b = list.new IntBlock(1);
}
}
/**
Visibilité depuis la classe interne
Chaque instance de la classe interne peut accéder à l'instance de la classe
externe à laquelle elle est liée avec la notation
NomClasseExterne . this
Par ce biais, la classe interne peut accéder à tous les champs de la classe
externe, y compris les champs privés. En particulier, pour les attributs de
classe la classe interne accèdera à la valeur partagée, et pour les attributs
d'instance l'instance de la classe interne accèdera à la valeur de l'attribut
pour l'instance à laquelle elle est liée.
Cela aurait permis la variante suivante.
*/
class IntList2 {
private IntBlock firstBlock;
class IntBlock {
private IntBlock nextBlock;
private final Integer elt;
public IntBlock(int n) {
this.elt = n;
}
// Nous pouvons donc ajouter dans la classe interne une méthode
// de la forme suivante.
public boolean isFirstBlock() {
return this == IntList.this.firstBlock;
}
// Note : de même que [this] peut être omis lors de l'accès aux champs,
// on aurait pu dans cet exemple noter seulement
// return this == firstBlock;
}
}
/**
Classes internes de classe
Commes les autres champs, les classes internes peuvent être d'instance
(par défaut) ou de classe (si qualifiées par [static]).
Rappel des différences entre les deux :
- un attribut d'instance est un élément de l'état de chaque instance, ses
valeurs pour chaque instance peuvent différer et évoluent indépendamment,
on y accède exclusivement à partir d'une instance
- un attribut de classe possède une seule valeur, partagée par toutes les
instances de la classe ; les modifications apportées sur une instance sont
répercutées sur les autres instances ; on peut y accéder à partir d'une
instance ou à partir du nom de la classe
- une méthode d'instance est appelée à partir d'une instance, qui est l'un
des paramètres de la méthode (qu'on appelle le paramètre implicite, et
auquel on peut faire référence avec [this])
- une méthode de classe peut être appelée à partir d'une instance ou
directement avec le nom de la classe ; dans tous les cas, une méthode de
classe n'a pas de paramètre implicite (une référence à [this] serait
rejetée)
- on a déjà vu qu'une d'instance d'une classe interne d'instance (la version
par défaut présentée ci-dessus), est liée à une instance de la classe
externe, et que cette instance de la classe externe correspondait à un
paramètre implicite du constructeur de la classe interne
À l'inverse, une instance d'une classe interne de classe n'est pas liée
à une instance particulière de la classe externe, mais à la classe externe
en général. En particulier, le constructeur d'une classe interne de classe
ne prend pas de paramètre implicite, et les restrictions précédentes sur la
construction ne s'appliquent donc pas.
En conséquence, une classe interne de classe n'a accès qu'aux champs de
classe (publics ou privés). Elle peut y accéder soit directement par leur
nom, soit sous la forme
NomClasseExterne . NomChamp
*/
/**
Classes paramétrées et classes internes
Les paramètres de type d'une classe externe sont transmis à ses classes
internes, sans qu'il faille réutiliser la notation [<T>] (le faire serait
interprété comme un paramètre supplémentaire, s'appliquant uniquement à
la classe interne, et qu'on aurait par malheur désigné par le même nom).
Une version paramétrique de l'exemple précédent serait donc comme suit.
*/
class List<T> {
private Block firstBlock;
// La classe interne [Block] ne prend pas de paramètre de type
// supplémentaire, mais peut en revanche faire référence à [T].
class Block {
private Block nextBlock;
private final T elt;
public Block(T n) { this.elt = n; }
public T getElt() { return this.elt; }
}
public void addHead(T n) {
Block b = new Block(n);
b.nextBlock = this.firstBlock;
this.firstBlock = b;
}
protected Block getFirstBlock() { return this.firstBlock; }
}
class TestTList {
public static void main(String[] args) {
List<Integer> list = new List<>();
list.addHead(1);
list.addHead(2);
list.addHead(4);
// Vu de l'extérieur, le nom de [Block] est préfixé par [List]
// appliqué à un paramètre de type.
List<Integer>.Block b = list.getFirstBlock();
// List<Integer>.Block c = b.nextBlock; // Impossible
System.out.println(b.getElt());
}
}