Vai alla Home Page About me Courseware Federica Living Library Federica Federica Podstudio Virtual Campus 3D Le Miniguide all'orientamento Gli eBook di Federica La Corte in Rete
 
 
Il Corso Le lezioni del Corso La Cattedra
 
Materiali di approfondimento Risorse Web Il Podcast di questa lezione

Marco Faella » 16.Parametri di tipo con limiti


Limiti superiori

Quando si dichiara un parametro di tipo, appartenente ad una classe oppure ad un metodo, si può specificare che quel parametro potrà assumere come valore solo sottotipi di un tipo dato.

Questo tipo viene chiamato limite superiore per il parametro in questione.
Un parametro di tipo può anche avere più limiti superiori simultaneamente

  • in questo caso, il parametro attuale di tipo dovrà essere sottotipo di ciascuno dei limiti superiori.

La sintassi per indicare un o più limiti superiori è la seguente:

<T extends U1 & U2 & ... >

U1, U2, etc. sono tutte interfacce, oppure
U1 è una classe e i successivi (eventuali) tipi sono tutti interfacce.

In altre parole, solo uno dei limiti superiori può essere una classe

  • questo perché non avrebbe senso pretendere che il parametro attuale estenda simultaneamente due classi.

La versione parametrica di Comparable e Comparator

Le due interfacce Comparable e Comparator, già oggetto di una lezione precedente, sono in realtà parametriche:

public interface Comparable<T> {

public int compareTo(T x);

}

public interface Comparator<T> {

public int compare(T x, T y);

}

Come si vede, il parametro di tipo permette di specificare che tipo di oggetti tali metodi sono in grado di confrontare.
Ad esempio, se la classe Employee intende fornire un ordinamento naturale tra impiegati (ad esempio, ordinamento alfabetico per nome), essa si presenterà come segue:

public class Employee implements Comparable<Employee>.

Esempio

Supponiamo di voler scrivere un metodo statico che accetta una lista di oggetti tra loro confrontabili, e restituisce l’elemento minimo della lista.
I limiti superiori permettono di accettare, come parametro di tipo, soltanto un tipo che implementi Comparable.
Siccome Comparable è a sua volta parametrica, bisogna specificare anche il suo parametro di tipo.
Otteniamo quindi la seguente soluzione:

Mostra codice

Esempio

Se Employee implementa Comparable<Employee>, è possibile passare al metodo precedente una lista di Employee.
Supponiamo poi che la classe Manager estenda Employee, e non implementi direttamente nessuna versione di Comparable.
Indirettamente, Manager implementerà anch’essa Comparable<Employee>.
In questa situazione, non è possibile passare al metodo getMin una lista di Manager, perché la classe Manager non implementa Comparable<Manager>.
Le prossime slide mostrano come ovviare a questa limitazione.

Tipi parametrici e la relazione di sottotipo

Una delle regole di sottotipo afferma che se A è sottotipo di B, array di A è sottotipo di array di B.
Questo principio non vale per i tipi parametrici.
Ovvero, se X è una classe con un parametro di tipo, ed A e B sono due tipi tali che A è sottotipo di B, non è vero che X<A> è sottotipo di X<B>.A questo proposito, è interessante confrontare il comportamento di array e collezioni, rispetto ai sottotipi.

Nel caso degli array, consideriamo il seguente frammento di codice:

Manager[] man_arr = new Manager[10];
Employee[] emp_arr = man_arr;
emp_arr[0] = new Employee(...);

Questo frammento compila correttamente, grazie alle regole di sottotipo per array.
Tuttavia, il terzo rigo provoca un’eccezione al run-time.
Infatti, non è possibile aggiungere un Employee ad un array che è stato dichiarato.
La prossima slide illustra cosa succede in una situaziona analoga, che utilizza ArrayList invece di semplici array.

Tipi parametrici e la relazione di sottotipo

Possiamo tentare di sviluppare un esempio simile a quello della slide precedente, sostituendo gli array con oggetti di tipo ArrayList

ArrayList<Manager> man_list = new ArrayList<Manager>();
ArrayList<Employee> emp_list = man_list;
emp_list.add(new Employee(...));

Questa volta, il frammento di codice provoca un errore di compilazione.
In particolare, l’errore si verifica al secondo rigo, perché ArrayList<Manager> non è sottotipo di (e quindi non è assegnabile a) ArrayList<Employee>.
La situazione è quindi migliore della precedente, perché quello che prima era un errore al run-time ora è diventato un errore di compilazione, molto più facile da individuare, e quindi correggere.

Concludiamo che il sistema dei tipi parametrici è più robusto di quello degli array, rispetto agli errori di tipo.
Infatti, le regole relative ai tipi parametrici garantiscono che, se il programma viene compilato senza warning e se non utilizza array o cast, non possono verificarsi errori di tipo al run-time.

Il parametro di tipo jolly

Le regole appena viste per la relazione di sottotipo tra tipi parametrici comportano, tra l’altro, che List<Object> non sia il supertipo comune a tutte le liste.
Quindi, sembrerebbe che, se un metodo intende accettare liste di ogni tipo, esso debba necessariamente essere parametrico.
Invece, il parametro di tipo jolly, rappresentato sintatticamente da un punto interrogativo, permette di raggiungere lo stesso scopo senza utilizzare parametri di tipo.
Ovvero, List<?> è il supertipo comune a tutte le versioni di List.

Il parametro di tipo jolly si può usare solamente come parametro attuale di tipo, ed intuitivamente rappresenta un tipo sconosciuto.

Non c’è nessuna relazione tra due diverse occorrenze del parametro jolly.
Ad esempio, il seguente metodo accetta due liste dello stesso tipo

public static <T> void addAll(List<T> l1, List<T> l2) { ... }

mentre il seguente metodo accetta due liste qualsiasi

public static void addAll(List<?> l1, List<?> l2) { ... }

Il parametro di tipo jolly (segue)

Supponiamo che A<T> sia una classe parametrica.
Dichiarando un riferimento di tipo A<?>, è come se le occorrenze di T all’interno di A diventassero “?”, cioè, “tipo sconosciuto”.
Questo ha determinate conseguenze sulle chiamate a metodi fatte tramite tale riferimento.

In particolare, se un metodo della classe A<T> accetta un argomento di tipo T, usando un riferimento di tipo A<?> potremo passare a quel metodo solamente null.
Infatti, non sapendo qual è il tipo concreto che quel metodo si aspetta, null è l’unico valore che è lecito passargli.

Se invece un metodo della classe A<T> restituisce valori di tipo T, chiamandolo con un riferimento di tipo A<?> potremo assegnare il valore restituito solamente ad un riferimento di tipo Object.
Infatti, Object è l’unico tipo che sicuramente accetta qualunque tipo sconosciuto (esclusi i tipi base).

Parametro di tipo jolly con limiti superiori e inferiori

Come i normali parametri di tipo, anche il parametro di tipo jolly può essere dotato di un limite superiore, ma non più di uno.
Ad esempio, List<? extends Employee> è una lista di tipo sconosciuto che estende Employee.
Precisamente, List<? extends Employee> è il supertipo comune a tutte le liste il cui parametro di tipo estende Employee.

A differenza dei normali parametri di tipo, il tipo jolly può anche avere un limite inferiore.
Ad esempio, List<? super Employee> rappresenta una lista di un tipo sconosciuto che è supertipo di Employee (come una ipotetica classe Person, oppure Object).

La relazione di sottotipo tra diverse versioni di List.

La relazione di sottotipo tra diverse versioni di List.


Esempio

Consideriamo nuovamente il problema di scrivere un metodo che accetta una lista di oggetti tra loro confrontabili, e restituisce l’elemento minimo della lista.
Il parametro di tipo jolly permette di dichiarare il metodo in modo tale da accettare una lista di Manager, anche se Manager implementa Comparable<Employee>:

public static <T extends Comparable<? super T>> T getMin(List<T> l) { ... }

In effetti, un metodo molto simile si trova nella classe java.util.Collections della libreria standard:

public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> l) { ... }

I dettagli di questa firma verranno esaminati nella lezione successiva.

Limitazioni imposte dal parametro jolly

Come abbiamo visto per il tipo jolly senza limiti, l’uso del parametro jolly con limiti superiori o inferiori impone determinate condizioni sulle chiamate ai metodi.
Supponiamo che A<T> sia una classe parametrica.
La seguente tabella riassume le limitazioni che valgono per un riferimento di tipo A<?>, A<? extends B>, oppure A<? super B>, rispetto ad un metodo di A che accetta un argomento di tipo T, oppure restituisce un valore di tipo T.


  • Contenuti protetti da Creative Commons
  • Feed RSS
  • Condividi su FriendFeed
  • Condividi su Facebook
  • Segnala su Twitter
  • Condividi su LinkedIn
Progetto "Campus Virtuale" dell'Università degli Studi di Napoli Federico II, realizzato con il cofinanziamento dell'Unione europea. Asse V - Società dell'informazione - Obiettivo Operativo 5.1 e-Government ed e-Inclusion