Site icon Blog Zenika

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.

  1. public interface Iterable<T> {
  2. /**
  3.   * Returns an iterator over a set of elements of type T.
  4.   * @return an Iterator.
  5.   */
  6. Iterator<T> iterator();
  7. }

L’interface java.util.Collection étend Iterable, ce qui nous autorise à parcourir ses implémentations (notamment List et Set) de manière uniforme :

  1. List<String> words = Arrays.asList(« Hello », « World »);
  2. Iterator<String> it = words.iterator();
  3. while(it.hasNext()) {
  4. System.out.println(it.next());
  5. }

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.

  1. List<String> words = Arrays.asList(« Hello », « World »);
  2. for (String word : words) {
  3. System.out.println(word);
  4. }

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 :

  1. public class Partition {
  2. private String nom;
  3. private List<Note> notes;
  4. public String getNom() {
  5. return nom;
  6. }
  7. public void setNom(String nom) {
  8. this.nom = nom;
  9. }
  10. public List<Note> getNotes() {
  11. return notes;
  12. }
  13. public void setNotes(List<Note> notes) {
  14. this.notes = notes;
  15. }
  16. }

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é :

  1. public class Partition implements Iterable<Note> {
  2. private final String nom;
  3. private final List<Note> notes = new ArrayList<Note>();
  4. public Partition(String nom, List<Note> notes) {
  5. this.nom = nom;
  6. if (notes != null) {
  7. this.notes.addAll(notes);
  8. }
  9. }
  10. public String getNom() {
  11. return nom;
  12. }
  13. public Iterator<Note> iterator() {
  14. return notes.iterator();
  15. }
  16. }

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.

  1. Partition partition = new Partition(« Au clair de la lune », Arrays.asList(C, C, C, D, E, D, C, E, D, D, C));
  2. for(Note note : partition) {
  3. //Instrument.play(note);
  4. }

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 !

Auteur/Autrice

Quitter la version mobile