Au coeur du JDK : l'interface Iterable
Connaissez-vous l’interface java.lang.Iterable
?
Mais si, vous la connaissez : vous l’utilisez tous les jours, lorsque vous parcourez une Collection. Hé bien, voyons maintenant comment vous pouvez l’utiliser à votre avantage lorsque vous concevez un modèle objet.
Iterable, mode d’emploi
L’interface Iterable
signale que la classe qui l’implémente est composée d’un ensemble de sous-éléments que le code appelant peut parcourir.
Elle déclare pour cela une unique méthode iterator(), devant renvoyer un Iterator
sur l’ensemble des sous-éléments.
-
public interface Iterable<T> {
-
/**
-
* Returns an iterator over a set of elements of type T.
-
* @return an Iterator.
-
*/
-
Iterator<T> iterator();
-
}
L’interface java.util.Collection
étend Iterable
, ce qui nous autorise à parcourir ses implémentations (notamment List
et Set
) de manière uniforme :
-
List<String> words = Arrays.asList(“Hello”, “World”);
-
Iterator<String> it = words.iterator();
-
while(it.hasNext()) {
-
System.out.println(it.next());
-
}
C’est également Iterable qui vous permet d’utiliser la boucle “foreach” apparue avec Java 5 (JLS $14.14.2).
EnhancedForStatement:
for ( VariableModifiersopt Type Identifier: Expression) Statement
The Expression must either have type Iterable or else it must be of an array type (§10.1), or a compile-time error occurs.
-
List<String> words = Arrays.asList(“Hello”, “World”);
-
for (String word : words) {
-
System.out.println(word);
-
}
Implémenter Iterable dans votre code
Lorsque nous concevons un modèle objet, il arrive fréquemment que nous devions modéliser des relations de type “1-N avec attributs”. Leur implémentation la plus évidente est une classe possédant possédant des champs correspondant aux attributs de la relation, ainsi qu’une collection encapsulant les sous-éléments.
Une partition musicale en est un bon exemple : elle est constituée d’une séquence de notes (les sous-éléments), ainsi que de divers attributs comme le nom du morceau.
En voici une implémentation naïve :
-
public class Partition {
-
private String nom;
-
private List<Note> notes;
-
public String getNom() {
-
return nom;
-
}
-
public void setNom(String nom) {
-
this.nom = nom;
-
}
-
public List<Note> getNotes() {
-
return notes;
-
}
-
public void setNotes(List<Note> notes) {
-
this.notes = notes;
-
}
-
}
Ce type d’implémentation est très répandu, mais malheureusement très mauvais du point de vue de la conception objet, car il rompt le principe d’encapsulation : l’accesseur de la liste
des notes permet de modifier celle-ci sans que la Partition
ne soit avertie, et expose son implémentation interne.
Il faut donc trouver un moyen pour autoriser le développeur à parcourir la liste sans l’exposer directement.
C’est là qu’intervient l’interface Iterable.
Voici le code remanié :
-
public class Partition implements Iterable<Note> {
-
private final String nom;
-
private final List<Note> notes = new ArrayList<Note>();
-
public Partition(String nom, List<Note> notes) {
-
this.nom = nom;
-
if (notes != null) {
-
this.notes.addAll(notes);
-
}
-
}
-
public String getNom() {
-
return nom;
-
}
-
public Iterator<Note> iterator() {
-
return notes.iterator();
-
}
-
}
Ce remaniement amène un second bénéfice, pour l’utilisateur cette fois : il est beaucoup plus facile et naturel de parcourir la partition, note par note.
-
Partition partition = new Partition(“Au clair de la lune”, Arrays.asList(C, C, C, D, E, D, C, E, D, D, C));
-
for(Note note : partition) {
-
//Instrument.play(note);
-
}
Conclusion
En conclusion, lorsque vous encapsulez une collection au sein d’une classe, pensez à l’interface Iterable
; votre code sera mieux encapsulé et plus facile à utiliser !
Merci pour cet article.
J’avais voulu faire la même chose sur un projet, mais j’avais rencontré certains problèmes avec cette approche. J’ai commencé un commentaire, mais il est beaucoup trop long, je vais donc écrire un post en réponse (et me créer un blog du même coup 😉 ).
Sinon, la classe Partition de votre example n’est pas immutable. En effet, il est possible de supprimer des notes en appelant remove() sur l’Iterator obtenu via partition.getIterator().
Pour éviter ce problème, il faudrait utiliser un itérateur non modifiable, en créant un Iterator anonyme délégant à l’Iterator des notes, ou en utilisant un wrapper tel que l’UnmodifiableIterator de Guava (et sa factory-method: http://guava-libraries.googlecode.c… ) :
import com.google.common.collect.Iterators;
// …
public Iterator<Note> iterator() {
}
c’est beau, efficace, presque de quoi verser une larme :).