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 NumberSenza 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 = StringComparable<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 syntaxI 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
Tcome numeri" →<T extends Number>. - "Ho bisogno di leggere campi da
Tcome unUser" →<T extends User>. - "Ho bisogno che
Tsia 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.
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.