2016-02-28 16 views
30

Si consideri il frammento:Quale tipo di sicurezza avrebbe perso, se Generics avesse supportato la sub-tipizzazione?

Number[] numbers = {1, 2.3, 4.5f, 6000000000000000000L}; 

E 'perfettamente bene di fare quanto sopra, Number è una classe astratta.

Proseguendo,

List<Long> listLong = new ArrayList<Long>(); 
listLong.add(Long.valueOf(10)); 

List<Number> listNumbers = listLong; // compiler error - LINE 3 

listNumbers.add(Double.valueOf(1.23)); 

Aveva Linea 3 è stato progettato per essere compilato con successo, ci ritroveremmo con un List di Number s, cioè,

for(Number num: listNumbers){ 
    System.out.println(num); 
} 

// 10 
// 1.23 

quali sono tutti i numeri.

mi sono imbattuto in un libro,

Generics non supporta sub-tipizzazione perché causerà problemi in raggiungimento della sicurezza tipo. Ecco perché List<T> non è considerato come un sottotipo di List<S> dove S è il super-tipo di T

Quale tipo di sicurezza avrebbe perso in questo specifica caso, come discusso in precedenza, sono stati la linea 3 è stato per essere compilato con successo?

+1

Dai un'occhiata a questo articolo sul perché Java [i generici non sono covarianti] (http://www.ibm.com/developerworks/library/j-jtp01255/). – Ralf

+3

'Elenco 'funzionerebbe. Previene 'add'. –

risposta

41
List<Long> listLong = new ArrayList<Long>(); 
List<Number> listNumbers = listLong; 

Così, listNumbers e listLong sarebbero due riferimenti alla stessa lista, se possibile, giusto?

listNumbers.add(Double.valueOf(1.23)); 

Quindi, sarebbe possibile aggiungere un doppio a tale elenco. listLong, di tipo List<Long>, conterrebbe quindi un doppio. La sicurezza del tipo sarebbe quindi rotta.

+1

Come posso essere così stupido stamattina? –

+12

@ShirgillFarhanAnsari non lo sei, in realtà è una buona domanda :) – Maroun

+0

@ShirgillFarhanAnsari In C# hanno creato 'in' e' out' (Modificatore generico) per questo motivo. Cercare se si vuole conoscere una strategia che eviti il ​​problema indicato in questa risposta. – WorldSEnder

8

In tal caso, è possibile aggiungere altri sottotipi diversi di Number a listNumbers, che deve essere vietato.

Immagina di inserire ora oggetti di tipo Double e Long e in seguito si tenta di utilizzare Long#reverse. Il tuo codice comporterà il ma, ovviamente, non riuscirà al runtime (errato) il primo Double che verrà visualizzato.

5

Facciamo un esempio con una classe di base non astratta:

public class Human { 
    public string getName() { 
     // ... 
    } 
} 

public class Student extends Human { 
    public void learn(Subject subject) { 
     // ... 
    } 
} 

public class Teacher extends Human { 
    public void teach(Subject subject) { 
     // ... 
    } 
} 

in qualsiasi luogo in cui ci si aspetta un Human, un Student o Teacher farà altrettanto bene, come attuare pienamente l'interfaccia Human. (In questo caso, è possibile chiamare tale getName().) L'ereditarietà di Java garantisce che questo sia il caso tecnicamente. Rendendolo funzionare semanticamente è il lavoro dell'autore della classe, in modo che il suo codice soddisfi lo Liskov substitution principle.

Quindi, questo non significa che possiamo anche sostituire Collection<Teacher> dove è previsto uno Collection<Human>? Non sempre. Si consideri il seguente metodo:

public class Human { 
    // ... 

    public void join(Set<Human> party) { 
     party.add(this); 
    } 
} 

Ora, se Java ha permesso un Set<Student> per essere passato come partito, qualsiasi tentativo di non StudentHuman s per unire quel partito avrebbe dovuto fallire in fase di esecuzione.

Come regola generale, un contenitore di un sottotipo non è adatto se il ricevitore (callee in caso di un argomento di una funzione, chiamata in caso di un valore di ritorno funzione) vuole mettere qualcosa in esso, ma accettabile se la il ricevitore vuole solo estrarre materiale e usarlo. Un contenitore di un supertipo non è adatto se il destinatario desidera estrarre e utilizzare il materiale, ma è accettabile se il ricevitore inserisce sempre solo materiale. Di conseguenza, se il ricevente preleva entrambi elementi dalla raccolta e inserisce elementi nella raccolta, di solito devono richiedere una raccolta di un tipo fisso.

Il nostro metodo join mette solo Human s nel party, così abbiamo potuto anche permettere un Set<Object> o non generico Set o equivalentemente un Set<?>. Java ci permette di farlo con lower-bounded wildcards:

public class Human { 
    // ... 

    public void join(Set<? super Human> party) { 
     party.add(this); 
    } 
} 

per l'apertura alle possibilità verso sottoclassi, c'è upper-bounded wildcards:

public class Teacher extends Human { 
    public void teach(Subject subject, Set<? extends Student> schoolClass) { 
     for (Student student : class) { 
      student.learn(subject); 
     } 
    } 
} 

Ora, se mai sottoclasse Student, il passato schoolClass può essere un Set di anche quel sottotipo.

+0

Bella spiegazione. – Maroun

3

Il concetto a cui si fa riferimento è variance.

In altre parole, se S è un supertipo di T, è List<S> un sottotipo, supertipo, uguale tipo o unreleted a List<T>?

La risposta per List - e tutti gli altri generici Java * - è "non correlata", vale a dire invariante.

class SuperType {} 

class Type extends SuperType {} 

class SubType extends Type {} 

List<Type> list = ... 

List<SuperType> superList = list; 
superList.add(new SuperType()); 
// no, we shouldn't be able to add a SuperType to list 

List<SubType> subList = list; 
SubType item = subList.get(0); 
// no, there's not necessarily only SubType items in list 

* Java fa hanno la nozione di "uso-site" varianza, con wildcards (?). Ciò limiterà quali metodi è possibile chiamare.

List<Type> list = ... 

List<? super SubType> wildcardList = list; 
wildcardList.add(new SubType()); 
// but...everything we get() is an Object 

o

List<Type> list = ... 

List<? extends SuperType> wildcardList = list; 
SuperType item = wildcard.get(0); 
// but...it's impossible to add() 

FYI, alcune lingue hanno l'idea della varianza definition-site, per esempio Scala. Quindi List[Int] è in effetti un sottotipo di List[Number]. Ciò è possibile con collezioni immutabili (di nuovo, un insieme limitato di metodi), ma ovviamente non per quelle mutevoli.