Perché List non è un sottotipo di List
-
11-09-2019 - |
Domanda
public void wahey(List<Object> list) {}
wahey(new LinkedList<Number>());
La chiamata al metodo non digitare-check. Non riesco nemmeno a lanciare il parametro nel seguente modo:
wahey((List<Object>) new LinkedList<Number>());
Dalla mia ricerca, ho capito che la ragione per non permettere questo è tipo di sicurezza. Se ci hanno permesso di fare quanto sopra, allora potremmo avere la seguente:
List<Double> ld;
wahey(ld);
All'interno del metodo wahey, potremmo aggiungere alcune stringhe alla lista di input (come parametro mantiene un riferimento List<Object>
). Ora, dopo la chiamata al metodo, ld si riferisce ad una lista con un tipo List<Double>
, ma la lista attuale contiene alcuni oggetti String!
Questo sembra diversa al modo normale Java funziona senza farmaci generici. Per esempio:
Object o;
Double d;
String s;
o = s;
d = (Double) o;
Quello che stiamo facendo qui è essenzialmente la stessa cosa, tranne che questo passerà controlli a tempo di compilazione e solo non riescono a run-time. La versione con liste non verrà compilato.
Questo mi porta a credere che questo è puramente una decisione di progettazione per quanto riguarda le restrizioni di tipo di farmaci generici. Speravo di ottenere alcuni commenti su questa decisione?
Soluzione
Quello che state facendo l'esempio "senza farmaci generici" è un cast, che rende chiaro che si sta facendo qualcosa di tipo pericoloso. L'equivalente con i generici potrebbe essere:
Object o;
List<Double> d;
String s;
o = s;
d.add((Double) o);
che si comporta allo stesso modo (compilazione, ma non riesce a runtime). La ragione per non permettere il comportamento che stai chiedendo circa è perché permetterebbe impliciti di tipo-non sicuri azioni, che sono molto più difficili da notare nel codice. Ad esempio:
public void Foo(List<Object> list, Object obj) {
list.add(obj);
}
Questo sembra perfettamente bene e type-safe fino a quando si chiama in questo modo:
List<Double> list_d;
String s;
Foo(list_d, s);
Che guarda anche type-safe, perché è come il chiamante non necessariamente sa cosa Foo sta andando a che fare con i suoi parametri.
Quindi, in questo caso si dispone di due bit apparentemente type-safe di codice, che insieme finiscono per essere di tipo pericoloso. Questo è male, perché è nascosto e quindi difficile da evitare e più difficili da eseguire il debug.
Altri suggerimenti
Considerate se fosse ...
List<Integer> nums = new ArrayList<Integer>();
List<Object> objs = nums
objs.add("Oh no!");
int x = nums.get(0); //throws ClassCastException
Si sarebbe in grado di aggiungere qualcosa del tipo genitore alla lista, che non può essere quello che era precedentemente dichiarato, che, come l'esempio di cui sopra dimostra, provoca problemi di ogni genere. Così, non è consentito.
Non sono sottotipi di ogni altra causa come funzionano i farmaci generici. Ciò che si vuole è quello di dichiarare la funzione in questo modo:
public void wahey(List<?> list) {}
Poi volontà di accettare una lista di tutto ciò che si estende oggetto. È anche possibile fare:
public void wahey(List<? extends Number> list) {}
Questo vi permetterà di prendere in Elenchi di qualcosa che è una sottoclasse di numero.
Mi raccomando si prende in mano una copia di "Java Generics e collezioni" di Maurice Naftalin & Philip Wadler.
Ci sono essenzialmente due dimensioni di astrazione qui, l'elenco astrazione e l'astrazione del suo contenuto. E 'perfettamente bene al variare lungo l'elenco di astrazione - di dire, per esempio, che si tratta di una LinkedList o un ArrayList - ma non è bene per limitare ulteriormente i contenuti, per dire: questo (lista che contiene gli oggetti) è una (lista collegata che detiene solo numeri). Perché ogni riferimento che conosce come (lista che contiene gli oggetti) capisce, dal contratto di questo tipo, che può contenere qualsiasi oggetto.
Questo è molto diverso da quello che hai fatto nel codice di esempio non generici, in cui hai detto: trattare questa stringa come se fosse un doppio. Si sta invece cercando di dire: il trattamento di questa (lista che contiene solo numeri) come (lista che contiene nulla). E non lo fa, e il compilatore può rilevare, in modo da non farvi scappare con esso.
"Quello che stiamo facendo qui è essenzialmente la stessa cosa, tranne che questo passerà di compilazione controlli e solo non riescono a run-time. La versione con liste non lo farà compilazione ".
Quello che stai osservando ha perfettamente senso se si considera che lo scopo principale di generici Java è quello di ottenere il tipo di incompatibilità a fallire in fase di compilazione, invece di tempo di esecuzione.
Generics fornisce un modo per voi di comunicare il tipo di una raccolta al compilatore, in modo che possa essere controllato. Una volta che il compilatore conosce il Elemento tipo di raccolta, la compilatore può controllare che hai usato la raccolta in modo coerente e può inserire i calchi corrette sui valori essere portato fuori della collezione.
In Java, List<S>
non è un sottotipo di List<T>
quando S
è un sottotipo di T
. Questa regola fornisce la sicurezza di tipo.
Diciamo che ci permettono un List<String>
di essere un sottotipo di List<Object>
. Si consideri il seguente esempio:
public void foo(List<Object> objects) {
objects.add(new Integer(42));
}
List<String> strings = new ArrayList<String>();
strings.add("my string");
foo(strings); // this is not allow in java
// now strings has a string and an integer!
// what would happen if we do the following...??
String myString = strings.get(1);
Quindi, forzandolo fornisce digitare sicurezza, ma ha anche un inconveniente, è meno flessibile. Si consideri il seguente esempio:
class MyCollection<T> {
public void addAll(Collection<T> otherCollection) {
...
}
}
Ecco una collezione di T
di, si consiglia di aggiungere i lotti di un'altra raccolta. Non è possibile chiamare questo metodo con un Collection<S>
per un sottotipo di S
T
. Idealmente, questo è ok perché si sta solo aggiungendo elementi nella vostra collezione, non si sta modificando la raccolta dei parametri.
Per risolvere questo problema, Java offre quello che chiamano "jolly". I caratteri jolly sono un modo di fornire covarianza / controvarianza. Ora considerare quanto segue usando i caratteri jolly:
class MyCollection<T> {
// Now we allow all types S that are a subtype of T
public void addAll(Collection<? extends T> otherCollection) {
...
otherCollection.add(new S()); // ERROR! not allowed (Here S is a subtype of T)
}
}
Ora, con i caratteri jolly permettiamo covarianza nel tipo T e blocchiamo operazioni che non sono di tipo sicuro (ad esempio l'aggiunta di un elemento nella collezione). In questo modo si ottiene la flessibilità e il tipo di sicurezza.