Cours BUT Info

Ch. 7-8 — Interfaces et collections

Le contrat d'interface et l'implémentation multiple contre le problème du diamant, List/Set/Map et leurs complexités, le contrat equals/hashCode, les parcours avec Iterator, Comparable/Comparator et final sous toutes ses formes.

Le problème du diamant

Un Frogosaur héritant à la fois de Frog et de Dinosaur, eux-mêmes héritiers de Animal qui déclare talk() : quelle définition exécuter ? Ce « losange de la mort » rend l'héritage multiple de classes impraticable — et le transtypage explicite ne sauve rien. La réponse de Java : héritage simple de classes, implémentation multiple d'interfaces.

Une interface est un type

Une interface est un ensemble de déclarations de signatures de méthodes publiques. Une classe qui l'implémente doit définir un comportement pour chacune — et ses instances peuvent alors être vues comme du type de l'interface :

public interface Recyclable { public void recycle(); }
public class Paper implements Recyclable, Flammable {
    public void tear() { /* ... */ }
    public void recycle() { /* ... */ }
    public void burn() { /* ... */ }
}

Trois conséquences, toutes illustrées par le cours :

  • une référence typée interface n'autorise que les méthodes du contrat : après Recyclable r = new Paper(), r.recycle() compile mais r.tear() est refusé, même si l'objet réel sait déchirer — le compilateur ne laisse rien passer ;
  • instanceof est vrai pour toutes les interfaces implémentées, comme pour toute la branche de classes (Paper, Recyclable, Flammable, Object répondent tous true) ;
  • une méthode paramétrée par l'interface — aMethod(Recyclable r) — accepte tout type implémentant, là où la surcharge dupliquerait le code et où une surcharge sur Object imposerait un downcast à chaque appel.

Pour invoquer le spécifique (tear() sur une référence Recyclable), un downcast explicite est nécessaire : code compilable, mais pas forcément exécutable — c'est le late binding du chapitre 4 qui revient.

List, Set, Map : choisir sa structure

Collection<E> est le socle commun (implémentée par 33 classes) : add, contains, remove, size, isEmpty, toArray… Map<K,V> vit à part : association clé unique → valeur, avec put, get, containsKey, keySet, entrySet, values. Aucune de ces interfaces ne s'instancie — on instancie leurs implémentations. Et ne pas confondre Collection (l'interface) avec Collections, la boîte à outils de méthodes statiques : sort, reverse, shuffle, frequency, disjoint…

InterfaceImplémentationsÀ retenir
ListArrayList, LinkedListdoublons autorisés, ordonnée (≠ triée), accès par index
SetHashSet, LinkedHashSet, TreeSetunicité, ni doublons ni indice ; add sur un doublon n'a aucun effet
Map<K,V>HashMap, LinkedHashMap, TreeMapassociation clé → valeur ; ordre : aucun / insertion / naturel

Le choix de l'implémentation suit les opérations dominantes — les complexités du cours tranchent. Côté listes : get() en O(1) pour ArrayList contre O(n) pour LinkedList, mais remove(0) en O(1) pour LinkedList contre O(n) — on choisit ArrayList quand les ajouts se font en fin de collection, LinkedList quand ils se font à position aléatoire. Côté ensembles et tables : HashSet/LinkedHashMap sont en O(1) quand TreeSet/TreeMap paient leur tri en O(log n).

Le contrat equals/hashCode

Les structures HashXXX rangent les objets d'après hashCode(). Le contrat : deux objets égaux doivent retourner le même hashcode — l'inverse n'est pas vrai. Concrètement, equals() et hashCode() doivent être définis de manière coordonnée, sur les mêmes attributs, sans quoi deux objets égaux atterrissent dans deux cases différentes et les recherches échouent.

Parcours : for-each, Iterator et Iterable

Le for-each parcourt listes, ensembles et files — pas les Map, qu'on traverse via keySet() (les clés) ou entrySet() (les couples Map.Entry<K,V>). Sa limite : modifier la structure pendant le parcours lève une ConcurrentModificationException. L'itérateur explicite résout le cas :

Iterator<Integer> it = myList.iterator();
while (it.hasNext()) {
    int tmp = it.next();
    if (tmp % 2 == 0) { it.remove(); }
}

Les itérateurs sont fail-fast : toute modification de la collection autrement que par leurs méthodes dédiées est signalée. ListIterator ajoute le parcours arrière (previous, hasPrevious, indices) et l'insertion (add, set, remove). Rendre un objet itérable se fait en implémentant Iterable<T> et sa méthode iterator() — le cours le fait sur Student implements Iterable<Double>, itérable sur ses notes : for (Double grade : s) fonctionne alors sans exposer la liste interne.

Comparable et Comparator

Comparable<T> et sa méthode compareTo fixent l'ordre naturel d'une classe : c'est lui qu'invoque Collections.sort(list) sans second argument — l'exemple du cours trie des ComparablePerson par nom. Quand un seul critère ne suffit pas (parfois le nom, parfois l'âge), Comparator<T> et sa méthode compare(o1, o2) définissent un critère externe : ForenameComparator, AgeComparator, passés à sort(list, new AgeComparator()). Les méthodes des boîtes à outils sont surchargées pour accepter les deux.

final et héritage d'interfaces

final revient sous toutes les formes : constante initialisée à la déclaration ou au constructeur ; paramètre non réaffectable (on peut toujours modifier le contenu de l'objet ou du tableau reçu, jamais le réallouer) ; méthode impossible à redéfinir — utile pour empêcher qu'une sous-classe ne double les crédits d'un compte ; classe impossible à dériver, dont toutes les méthodes sont alors implicitement final (comme les méthodes private, que « redéfinir » crée sans aucun lien).

Entre interfaces, l'héritage est possible et multiple : une interface peut étendre plusieurs interfaces. Hériter plusieurs fois des mêmes signatures par des chemins différents est possible mais risqué (conflits, ambiguïtés) : à éviter. Depuis Java 8, les méthodes default offrent enfin une implémentation par défaut, redéfinissable ou non — à manier avec prudence mélangée à l'implémentation multiple.

A retenir

  • interface = contrat + type ; héritage simple de classes, implémentation multiple d'interfaces ;
  • List/Set/Map : le choix suit les opérations dominantes (accès direct ? unicité ? association clé → valeur ?) ;
  • hashCode cohérent avec equals, sur les mêmes attributs ;
  • itérateur fail-fast, it.remove() pour supprimer en parcourant ;
  • Comparable = ordre naturel, Comparator = critère alternatif.


coursOO_07.pdf

R2.01 · Cours 7 — Héritage, interface et structure de données · 38 diapositives

Consultation seule — téléchargement désactivé

Chargement du document…

coursOO_08.pdf

R2.01 · Cours 8 — Interface, comparabilité, itérabilité · 33 diapositives

Consultation seule — téléchargement désactivé

Chargement du document…

Dernière mise à jour le

Sur cette page