W3docs

Paramètres de type bornés en Java

Contraignez les génériques Java avec des paramètres de type bornés via extends — bornes simples et multiples.

Un paramètre de type borné est un paramètre de type accompagné d'une clause extends qui restreint les types pouvant le remplacer. Sans borne, T est traité comme Object — le compilateur n'a aucun moyen de savoir si T possède une méthode compareTo, intValue ou toute autre méthode. Avec une borne, vous faites une promesse au compilateur sur ce qu'est T, et en échange, le compilateur vous autorise à appeler les méthodes qu'implique cette promesse. Presque toutes les méthodes génériques intéressantes ont au moins une borne quelque part dans leur signature.

La forme de base : <T extends Bound>

La syntaxe utilise extends entre le paramètre et sa borne — même mot-clé que la borne soit une classe ou une interface :

public static <T extends Number> double sum(List<T> values) {
  double total = 0;
  for (T n : values) total += n.doubleValue();   // legal — T is-a Number
  return total;
}

Lisez <T extends Number> comme « tout T qui est un Number ou une sous-classe de celui-ci. » À l'intérieur du corps, vous pouvez désormais traiter toute valeur T comme un Number — appeler doubleValue(), intValue(), n'importe quelle méthode de l'API publique de Number.

Utilisez-le de la même façon que n'importe quelle autre méthode générique :

sum(List.of(1, 2, 3));            // T = Integer — fine
sum(List.of(1.5, 2.5));           // T = Double  — fine
sum(List.of(1L, 2L, 3L));         // T = Long    — fine
sum(List.of("a", "b"));           // ❌ String is not a Number

Sans la borne, sum ne pourrait même pas tenter d'appeler doubleValue()T serait effacé en Object, et Object n'a pas cette méthode. La borne est ce qui rend le corps légal.

extends fonctionne aussi pour les interfaces

En Java génériques, extends est surchargé — il signifie « est un sous-type de », que la borne soit une classe ou une interface. Il n'existe pas de mot-clé implements séparé dans une liste de paramètres de type :

public static <T extends Comparable<T>> T max(List<T> list) {
  T best = list.get(0);
  for (T candidate : list) {
    if (candidate.compareTo(best) > 0) best = candidate;
  }
  return best;
}

max(List.of(3, 1, 4, 1, 5, 9));            // T = Integer
max(List.of("Ada", "Grace", "Linus"));     // T = String

Comparable<T> est une interface, mais la syntaxe reste extends. Lisez <T extends Comparable<T>> comme « tout T qui est comparable à lui-même » — la contrainte est ce qui vous permet d'appeler candidate.compareTo(best) à l'intérieur de la boucle.

La petite particularité ici est le <T> à l'intérieur de la borne — c'est la forme auto-référentielle que nous avons vue dans les interfaces génériques. Elle garantit que compareTo accepte un T, et non n'importe quel Comparable. Sans cela, vous pourriez comparer un Integer à un String et le système de types ne s'en rendrait pas compte.

Bornes multiples

Un paramètre de type peut avoir plusieurs bornes, reliées par & :

public static <T extends Number & Comparable<T>> T maxNumber(List<T> list) {
  T best = list.get(0);
  for (T n : list) {
    if (n.compareTo(best) > 0) best = n;
  }
  return best;
}

Maintenant T doit être à la fois un Number et un Comparable<T>. À l'intérieur du corps, vous pouvez appeler n'importe quelle méthode de l'un ou de l'autre. Integer, Long, Double, BigDecimal satisfont tous les deux — vous pouvez en passer n'importe lequel.

Quelques règles :

  • La borne de classe (le cas échéant) doit venir en premier. <T extends Number & Comparable<T>> est légal ; <T extends Comparable<T> & Number> ne l'est pas.
  • Au plus une borne de classe. Java possède l'héritage simple pour les classes, donc cela découle des règles existantes du langage.
  • Nombre illimité de bornes d'interface. Empilez-en autant que vous en avez vraiment besoin ; en pratique, deux est le maximum habituel avant que la conception ne nécessite une refonte.

La règle « classe en premier » reflète le bytecode : l'effacement remplace T par sa borne la plus à gauche, donc l'emplacement le plus à gauche est spécial. Nous y reviendrons dans Type Erasure.

Bornes sur les paramètres de type au niveau de la classe

Les bornes ne sont pas réservées aux méthodes. Une classe ou une interface générique peut borner ses paramètres de type dans la déclaration :

public class SortedBag<T extends Comparable<T>> {
  private final List<T> items = new ArrayList<>();

  public void add(T item) {
    items.add(item);
    Collections.sort(items);
  }

  public T smallest() { return items.get(0); }
}

SortedBag<Integer> bag = new SortedBag<>();   // OK — Integer is Comparable<Integer>
// SortedBag<Object> bag2 = new SortedBag<>(); // ❌ Object is not Comparable<Object>

C'est l'endroit approprié pour une borne qui s'applique à toute la classe. Chaque méthode, chaque champ, chaque opération par défaut a accès à la contrainte.

Les bornes inférieures appartiennent aux wildcards, pas aux paramètres de type

Une confusion fréquente : les paramètres de type peuvent avoir des bornes supérieures (extends) mais pas des bornes inférieures (super). Ceci est légal :

public static <T extends Number> void foo(T x) { ... }

Ceci ne l'est pas :

public static <T super Integer> void bar(T x) { ... }   // ❌ no such syntax

Les bornes inférieures existent bien dans les génériques Java — mais elles ne vivent que sur les wildcards, le jeton ?, et non sur des paramètres de type nommés. Nous couvrirons l'histoire complète de ? extends / ? super dans le chapitre Wildcards ; pour l'instant, retenez simplement que super fonctionne sur ? et non sur T.

Quand ajouter une borne

Par défaut, il ne faut pas ajouter de borne — plus la contrainte est lâche, plus votre méthode accepte de types. Ajoutez-en une lorsque, et seulement lorsque, le corps a besoin d'appeler une méthode spécifique sur T :

  • « J'ai besoin de comparer des valeurs T » → <T extends Comparable<T>>.
  • « J'ai besoin d'additionner des valeurs T comme des nombres » → <T extends Number>.
  • « J'ai besoin de lire des champs de T comme un User » → <T extends User>.
  • « J'ai besoin que T soit auto-fermable pour l'utiliser dans un try-with-resources » → <T extends AutoCloseable>.

Si vous n'appelez pas de méthode sur T, vous n'avez pas besoin d'une borne — et en ajouter une de toute façon ne fait que restreindre votre API sans raison.

Un exemple concret : un between borné et un top trié

Deux méthodes, chacune avec un style de borne différent. between utilise Comparable<T> pour pouvoir borner une valeur ; topN utilise Number & Comparable<T> pour pouvoir à la fois comparer et rapporter une somme numérique.

java— editable, runs on the server

between accepte tout Comparable — y compris Character, qui est Comparable<Character> et n'a rien à voir avec les nombres. topN est plus restrictif — il a besoin à la fois de l'ordre (pour trier) et de valeurs numériques (pour sommer), il empile donc les deux bornes avec &. Chaque méthode demande exactement la capacité qu'elle utilise, ni plus ni moins.

La suite

Une borne sur un paramètre de type fixe un seul type au moment de l'appel — List<Integer> signifie « cette liste, cette méthode, ce type. » Parfois vous voulez être moins précis : « une liste de n'importe quel Number — que ce soit Integer ou Double, peu importe. » C'est ce à quoi servent les wildcards, qui résolvent l'une des plus grandes sources de confusion dans le système de types Java. Continuez vers Java Generic Wildcards.

Pratique

Pratique
Vous avez besoin d'une méthode qui trouve le plus grand élément d'une liste. Quelle signature vous permet d'appeler `compareTo` sur les éléments sans restreindre les appelants plus que nécessaire ?
Vous avez besoin d'une méthode qui trouve le plus grand élément d'une liste. Quelle signature vous permet d'appeler `compareTo` sur les éléments sans restreindre les appelants plus que nécessaire ?
Was this page helpful?