W3docs

Parametri di Tipo Limitati in Java

Vincola i tipi generici Java con parametri di tipo limitati usando extends — limiti singoli e multipli.

Un parametro di tipo limitato è un parametro di tipo con una clausola extends che restringe quali tipi possono occuparlo. Senza un limite, T viene trattato come Object — il compilatore non ha modo di sapere se T ha un metodo compareTo, intValue o qualsiasi altro. Con un limite, fai una promessa al compilatore su cosa è T, e in cambio il compilatore ti consente di chiamare i metodi che quella promessa implica. Quasi ogni metodo generico interessante ha almeno un limite da qualche parte nella sua firma.

La forma di base: <T extends Bound>

La sintassi è extends tra il parametro e il suo limite — stessa parola chiave sia che il limite sia una classe o un'interfaccia:

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;
}

Leggi <T extends Number> come "qualsiasi T che è un Number o una sua sottoclasse." All'interno del corpo, puoi ora trattare qualsiasi valore T come un Number — chiamare doubleValue(), intValue(), qualsiasi cosa nell'API pubblica di Number.

Usalo nello stesso modo di qualsiasi altro metodo generico:

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

Senza il limite, sum non potrebbe nemmeno provare a chiamare doubleValue()T verrebbe cancellato in Object, e Object non ha quel metodo. Il limite è ciò che rende il corpo legale.

extends funziona anche per le interfacce

Nei generici Java, extends è sovraccaricato — significa "è un sottotipo di," sia che il limite sia una classe o un'interfaccia. Non esiste una parola chiave implements separata in un elenco di parametri di tipo:

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> è un'interfaccia, ma la sintassi è comunque extends. Leggi <T extends Comparable<T>> come "qualsiasi T che è comparabile a se stesso" — il vincolo è ciò che ti permette di chiamare candidate.compareTo(best) all'interno del ciclo.

La piccola particolarità qui è il <T> all'interno del limite — è la forma auto-referenziale che abbiamo incontrato nelle interfacce generiche. Garantisce che compareTo accetti un T, non solo qualsiasi Comparable. Senza di esso, potresti confrontare Integer con String e il sistema dei tipi non se ne accorgerebbe.

Limiti multipli

Un parametro di tipo può avere più di un limite, uniti da &:

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;
}

Ora T deve essere sia un Number che un Comparable<T>. All'interno del corpo puoi chiamare qualsiasi metodo di entrambi. Integer, Long, Double, BigDecimal soddisfano entrambi — puoi passare qualsiasi di essi.

Alcune regole:

  • Il limite di classe (se presente) deve venire per primo. <T extends Number & Comparable<T>> è legale; <T extends Comparable<T> & Number> non lo è.
  • Al massimo un limite di classe. Java ha ereditarietà singola per le classi, quindi questo deriva dalle regole esistenti del linguaggio.
  • Qualsiasi numero di limiti di interfaccia. Aggiungi quanti ne hai genuinamente bisogno; in pratica, due è il massimo usuale prima che il design necessiti di una revisione.

La regola classe-prima riflette il bytecode: la cancellazione sostituisce T con il suo limite più a sinistra, quindi lo slot più a sinistra è speciale. Ritorneremo su questo in Type Erasure.

Limiti sui parametri di tipo a livello di classe

I limiti non sono unici dei metodi. Una classe o interfaccia generica può limitare i suoi parametri di tipo nella dichiarazione:

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>

Questo è il posto giusto per un limite che si applica all'intera classe. Ogni metodo, ogni campo, ogni operazione predefinita ha accesso al vincolo.

I limiti inferiori appartengono ai wildcard, non ai parametri di tipo

Una confusione comune: i parametri di tipo possono avere limiti superiori (extends) ma non limiti inferiori (super). Questo è legale:

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

Questo non lo è:

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

I limiti inferiori esistono nei generici Java — ma vivono solo sui wildcard, il token ?, non sui parametri di tipo con nome. Tratteremo l'intera storia di ? extends / ? super nel capitolo sui Wildcards; per ora, sappi solo che super funziona con ? e non con T.

Quando aggiungere un limite

L'impostazione predefinita dovrebbe essere "nessun limite" — più il vincolo è allentato, più tipi accetta il tuo metodo. Aggiungine uno quando, e solo quando, il corpo deve chiamare un metodo specifico su T:

  • "Ho bisogno di confrontare valori T" → <T extends Comparable<T>>.
  • "Ho bisogno di sommare valori T come numeri" → <T extends Number>.
  • "Ho bisogno di leggere campi da T come un User" → <T extends User>.
  • "Ho bisogno che T sia auto-chiudibile per poterlo usare in try-with-resources" → <T extends AutoCloseable>.

Se non stai chiamando un metodo su T, non hai bisogno di un limite — e aggiungerne uno comunque restringe solo la tua API senza motivo.

Un esempio pratico: un between limitato e un top ordinato

Due metodi, ciascuno con uno stile diverso di limite. between usa Comparable<T> per poter limitare; topN usa Number & Comparable<T> per poter sia confrontare che riportare una somma numerica.

java— editable, runs on the server

between accetta qualsiasi Comparable — incluso Character, che è Comparable<Character> e non ha nulla a che fare con i numeri. topN è più ristretto — ha bisogno sia di ordinamento (per ordinare) che di valori numerici (per sommare), quindi combina i due limiti con &. Ogni metodo richiede esattamente la capacità che utilizza, niente di più.

Cosa c'è dopo

Un limite su un parametro di tipo fissa un singolo tipo al momento della chiamata — List<Integer> significa "questa lista, questo metodo, questo tipo." A volte vuoi essere meno specifico: "una lista di qualche Number — potrebbe essere Integer, potrebbe essere Double, non importa." Questo è ciò per cui esistono i wildcard, e risolvono una delle maggiori fonti di confusione nel sistema dei tipi Java. Continua con Java Generic Wildcards.

Esercitazione

Pratica
Hai bisogno di un metodo che trovi l'elemento più grande di una lista. Quale firma ti permette di chiamare `compareTo` sugli elementi senza limitare i chiamanti più del necessario?
Hai bisogno di un metodo che trovi l'elemento più grande di una lista. Quale firma ti permette di chiamare `compareTo` sugli elementi senza limitare i chiamanti più del necessario?
Was this page helpful?