Généricité

Il arrive souvent que l'on écrive du code qui mentionne un type précis de données, mais fonctionnerait tout aussi bien avec un autre type. Pour ne pas avoir à se répéter, on essaye de trouver une formulation polymorphe de ce code. Le principe de substitution de Liskov est un premier outil qui va dans ce sens, mais il est imparfait.

public static Object troisieme(Object[] tab) {
  return tab[2];
}
 
public static void main(String[] args) {
  String argument = (String) troisieme(args);
  ...
}

On constate qu'un transtypage est nécessaire car le type exact de la valeur de retour ne peut pas être précisé dans le code polymorphe. Il est possible de faire en mieux en donnant un nom au type inconnu :

public static <T> T troisieme(T[] tab) {
  return tab[2];
}
 
public static void main(String[] args) {
  String argument = troisieme(args);
  ...
}

Dans cet exemple T est un type paramètre de la méthode troisieme. Lors de l'invocation de la méthode, le compilateur détermine automatiquement que le type inconnu est en fait String, et renvoie directement le bon type de valeur.

Le même principe peut s'appliquer aux classes, tout particulièrement les classes conçues pour stocker des éléments de type quelconque.

public class Paire<E> {
  private E elementUn;
  private E elementDeux;
  public Paire(E e1, E e2) {
    this.elementUn = e1;
    this.elementDeux = e2;
  }
  public E get(int index) throws IndexOutOfBoundsException {
    if (index == 0) {
      return this.elementUn;
    } else if (index == 1) {
      return this.elementDeux;
    } else {
      throw new IndexOutOfBoundsException("La valeur "+index+" n'est pas acceptable !");
    }
  }
}

Remarque Les méthodes de cette classe peuvent mentionner le type paramètre de la classe sans pour autant devenir des méthodes génériques.

Pour employer une classe générique, il faut préciser le type qui va remplacer le type paramètre de la classe. Mais il arrive que l'on n'ait pas besoin (ni envie) de préciser ce type ; on emploie alors un type joker.

public static void afficherPaire(Paire<?> p) {
  System.out.println("{" + p.get(0) + ", " + p.get(1) + "}"); 
}

On peut également utiliser un type joker afin de rester souple, tout en garantissant un minimum de fonctionnalité grâce à une limite supérieure :

public static void ajouterComposants(LinkedList<? extends JComponent> liste, Container conteneur) {
  conteneur.setLayout(new GridLayout(liste.size(), 1));
  for(JComponent composant : liste) {
    conteneur.add(composant);
  }
}

Et enfin, il arrive (plus rarement) que l'on souhaite imposer au contraire une limite inférieure :

public static <T> void remplirPaire(Paire<? super T> destination, T e1, T e2) {
  destination.set(0, e1);
  destination.set(1, e2);
}

  1. Listes. La classe ArrayList<E> utilise un tableau redimensionnable pour stocker une liste d'éléments.

    1. Construisez une liste de Integer, une liste de Float et une liste de Number. Ajoutez-leur des éléments à l'aide de la méthode add. Quelles listes peuvent recevoir des Integer ? Quelles listes peuvent recevoir des Float ? Quelles listes peuvent recevoir des Long ?
    2. La méthode addAll permet de transvaser une liste dans une autre. Examinez sa définition. Pouvez-vous prédire quelle liste peut être transvasée dans quelle autre ? Faites le test.

  2. Tableaux. La classe Arrays contient de nombreuses méthodes facilitant la manipulation des tableaux.

    1. Écrivez un programme qui affiche à la console tous les arguments de sa ligne de commande. Plutôt qu'une boucle, utilisez pour cela une des méthodes toString de la classe Arrays.
    2. Affichez seulement les cinq premiers arguments de la ligne de commande ; appuyez-vous sur une des méthodes copyOf de la classe Arrays.
    3. On souhaite maintenant afficher les arguments dans l'ordre du dictionnaire. La classe Arrays offre une fois encore une méthode adaptée : sort. Malheureusement, comparer deux String n'est pas le même travail que comparer deux Integer, donc la méthode sort a besoin d'un objet qui se charge de cette tâche : un Comparator<T>. Pouvez-vous expliquer le rôle du mot-clé super dans la définition de la méthode sort ?
      Remarque Comparator<T> est en fait une interface, vous devrez donc trouver dans la documentation une classe qui la concrétise.

  3. Fréquence. On souhaite concevoir une méthode qui prend un tableau en argument et renvoie l'élément qui s'y répète le plus. En cas d'égalité, l'élément d'indice le plus faible sera privilégié. Cette méthode devra marcher pour des tableaux contenant tous types d'objet.

  4. Association. La méthode de l'exercice précédent ne donne pas toutes les informations que l'on pourrait désirer. Pour lui permettre de renvoyer à la fois l'élément le plus répété et sa fréquence (le nombre de répétition), on va construire une classe Association contenant un élément (de type variable) et une fréquence (de type entier). Il faudra prévoir des accesseurs pour chaque attribut, un constructeur et une surcharge de la méthode toString. Dans un diagramme de classe, ça se représenterait ainsi :

            E
        Association  
             
    - element : E
    - frequence : entier
     
    + getElement() : E
    + setElement(in elt : E)
    + getFrequence() : entier
    + setFrequence(in f : entier)
    + toString() : String
     

  5. Fréquences. On peut encore améliorer les résultats de la méthode de l'exercice précédent : on souhaite à présent obtenir la liste de tous les éléments du tableau, avec la fréquence de chacun d'entre eux.

    1. Transformez la classe Association en lui ajoutant un champ suivant pour en faire une liste chaînée.
    2. Définissez une classe Liste qui contient l'adresse du premier maillon de la liste, et qui possède une surcharge de la méthode toString pour afficher le contenu de toute la liste.
    3. Donnez à la classe Liste une méthode ajouter qui prend un élément en argument. Si l'argument est déjà présent dans la liste, sa fréquence doit être augmentée de 1. Dans le cas contraire, un nouveau maillon contenant l'élément et une fréquence de 1 est ajouté à la liste.
    4. Modifiez la méthode de l'exercice précédent pour qu'elle renvoie un objet de la classe Liste qui contient les fréquences de tous les éléments présents dans le tableau.
    5. Pour les plus courageux, enlevez toutes les boucles de cet exercice en vous appuyant sur la récursivité.

retour à la page d'accueil

retour au sommet