Problema
Si considerino la seguente classe Person e le classi figlie Manager e Employee definite come segue:
public static class Person {
String name;
public Person(String name) { this.name = name; }
}
public static class Manager extends Person{
public Manager(String name) { super(name); }
}
public static class Employee extends Person{
public Employee(String name) { super(name); }
}
Code language: JavaScript (javascript)
Si consideri ora un generico metodo statico che accetta due parametri di un tipo generico T e ritorna un oggetto di tipo T . Al tipo T viene inserito il vincolo <T extends Person> ossia il tipo T dovrà possedere ALMENO ciascuno degli attributi e dei metodi di person.
public static T foo(T t1, T t2){
//Do something
return …
}
Code language: PHP (php)
Si supponga di definire le seguenti istanze p1 e p2 e di effettuare la seguente invocazione del metodo foo() :
Employee p1 = new Employee("John");
Manager p2 = new Manager("Johanna");
Person out = foo(p1, p2);
Code language: JavaScript (javascript)
Si vuole ora imporre che entrambi i parametri passati al metodo foo() siano dello stesso tipo facendo in modo che il compilatore segnali un errore in caso contrario.
Osservazione
Eseguendo l’invocazione del metodo sopra riportata NON viene generato alcun errore di compilazione in quanto sia p1 (di tipo Employee) che p2 (di tipo Manager) rispettano i vincoli imposti dal tipo generico T (infatti essi possiedo entrambi gli attributi di Person).
Per comprendere al meglio il problema è necessario analizzare i meccanismi di compilazione e precompilazione del codice.
Il ruolo del type erasure
Il type erasure è il meccanismo di precompilazione che provvede a rimpiazzare tutti i tipi generici con il tipo di oggetto con maggior compatibilità (rispettando eventuali limitazioni poste).
Nel caso in esame, poiché viene fissato il vincolo <T extends Person>, il tipo generico T sarà rimpiazzato con il tipo di oggetto in grado di garantire la miglior compatibilità e di rispettare le limitazioni imposte. Si otterrà perciò:
public static Person foo(Person t1, Person t2){
//Do something
return …
}
Code language: PHP (php)
Risulta quindi chiaro come, effettuando l’invocazione proposta sopra, sia p1 che p2 siano accettati dal compilatore, in quanto entrambi “discendenti” di Person.
Type inference
In caso vengano passati come parametri di foo() istanze di oggetti dello stesso tipo entra in gioco il ruolo del meccanismo di type inference che provvede ad utilizzare come tipo di compilazione la sotto-classe delle istanze passate come argomenti.
Nel caso in oggetto essendo entrambe le classi, Employee e Manager, figlie dirette di Person, la classe più adatta da utilizzare al momento della compilazione sarà Person.
In caso venissero passate al metodo due istanze di Manager, la classe più adatta da utilizzare al momento della compilazione sarà Manager.
Possibili soluzioni al problema
Alla luce di quanto osservato finora sembra di aver rilevato un limite imposto dai tipi generici: nel caso vengano passati ad un metodo due istanze di tipo generico T limitato tramite la sintassi <T extends C>, se le istanze passate come parametri del metodo sono entrambe sotto-classi di C non è possibile produrre errori in compile-time in caso le istanze passate come parametri siano di tipi differenti.
Sono illustrati di seguito possibili soluzioni alternative al problema.
Soluzione alternativa 1 – Classe ausiliaria
Nel caso si conosca il numero di parametri dello stesso tipo da passare al metodo, definire un classe con tipo generico che accetti i vari oggetti e venga successivamente passata come unico parametro del metodo foo().
//New class with two same-type objects
public static class Pair<P>{
private P first;
private P second;
public Pair(P first, P second) {
this.first = first;
this.second = second;
}
public P getFirst() {
return first;
}
public P getSecond() {
return second;
}
}
//New foo definition
public static <T extends Person> T foo(Pair<T> pair){
//Do something
return ...
}
Employee p1 = new Employee("John");
Employee p2 = new Employee("Francis");
Manager p3 = new Manager("Johanna");
//Accepted
Pair<Employee> pair = new Pair<>(p1, p2);
Person out = foo(pair);
//Compile error (p1 e p3 are different types)
Pair<Employee> pair2 = new Pair<>(p1, p3);
Person out = foo(pair2);
Code language: PHP (php)
Soluzione alternativa 2 – Verifica in run-time
Tramite l’utilizzo del metodo getClass() è possibile verificare a run-time se i parametri passati sono istanze di oggetti dello stesso tipo:
public static <T extends Person> T my_method(T t1, T t2){
if(t1.getClass() != t2.getClass()){
throw new IllegalArgumentException("Different type arguments");
}
//Do something
return ...
}
Code language: PHP (php)