Ad esempio, supponiamo che tu abbia due classi:
public class TestA {}
public class TestB extends TestA{}
Ho un metodo che restituisce a List<TestA>e vorrei lanciare tutti gli oggetti in quell'elenco in TestBmodo da finire con a List<TestB>.
Ad esempio, supponiamo che tu abbia due classi:
public class TestA {}
public class TestB extends TestA{}
Ho un metodo che restituisce a List<TestA>e vorrei lanciare tutti gli oggetti in quell'elenco in TestBmodo da finire con a List<TestB>.
Risposte:
Semplicemente casting per List<TestB>quasi funzionare; ma non funziona perché non puoi trasmettere un tipo generico di un parametro a un altro. Tuttavia, è possibile eseguire il cast di un tipo di carattere jolly intermedio e sarà consentito (poiché è possibile eseguire il cast da e verso i tipi di carattere jolly, solo con un avviso non selezionato):
List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;
il casting di generici non è possibile, ma se si definisce l'elenco in un altro modo è possibile memorizzare TestB in esso:
List<? extends TestA> myList = new ArrayList<TestA>();
È ancora necessario eseguire il controllo del tipo quando si utilizzano gli oggetti nell'elenco.
Cannot instantiate the type ArrayList<? extends TestA>.
Non puoi davvero *:
L'esempio è tratto da questo tutorial Java
Supponiamo che ci siano due tipi Ae Btale B extends A. Quindi il seguente codice è corretto:
B b = new B();
A a = b;
Il codice precedente è valido perché Bè una sottoclasse di A. Ora, cosa succede con List<A>e List<B>?
Si scopre che List<B>non è una sottoclasse di List<A>quindi non possiamo scrivere
List<B> b = new ArrayList<>();
List<A> a = b; // error, List<B> is not of type List<A>
Inoltre, non possiamo nemmeno scrivere
List<B> b = new ArrayList<>();
List<A> a = (List<A>)b; // error, List<B> is not of type List<A>
*: Per rendere possibile il casting, abbiamo bisogno di un genitore comune per entrambi List<A>e List<B>: List<?>per esempio. È valido quanto segue :
List<B> b = new ArrayList<>();
List<?> t = (List<B>)b;
List<A> a = (List<A>)t;
Riceverai comunque un avviso. Puoi sopprimerlo aggiungendo @SuppressWarnings("unchecked")al tuo metodo.
Con Java 8, in realtà è possibile
List<TestB> variable = collectionOfListA
.stream()
.map(e -> (TestB) e)
.collect(Collectors.toList());
new List<TestB>, un loop e un cast?
Penso che stai lanciando nella direzione sbagliata però ... se il metodo restituisce un elenco di TestAoggetti, allora non è davvero sicuro lanciarli TestB.
Fondamentalmente stai chiedendo al compilatore di permetterti di eseguire TestBoperazioni su un tipo TestAche non li supporta.
Dato che questa è una domanda ampiamente referenziata e le risposte attuali spiegano principalmente perché non funziona (o propongono soluzioni pericolose, che non vorrei mai vedere nel codice di produzione), penso che sia opportuno aggiungere un'altra risposta, mostrando le insidie e una possibile soluzione.
Il motivo per cui ciò non funziona in generale è già stato sottolineato in altre risposte: la validità effettiva della conversione dipende dai tipi di oggetti contenuti nell'elenco originale. Quando ci sono oggetti nella lista il cui tipo non è di tipo TestB, ma di una sottoclasse diversa di TestA, allora il cast non è valido.
Naturalmente, i cast possono essere validi. A volte hai informazioni sui tipi che non sono disponibili per il compilatore. In questi casi, è possibile eseguire il cast degli elenchi, ma in generale non è consigliabile :
Uno potrebbe o ...
Le implicazioni del primo approccio (che corrisponde alla risposta attualmente accettata) sono sottili. A prima vista potrebbe sembrare che funzioni correttamente. Ma se ci sono tipi errati nell'elenco di input, allora ClassCastExceptionverrà lanciato un, forse in una posizione completamente diversa nel codice, e potrebbe essere difficile eseguire il debug di questo e scoprire dove l'elemento sbagliato è scivolato nell'elenco. Il problema peggiore è che qualcuno potrebbe persino aggiungere gli elementi non validi dopo che l'elenco è stato lanciato, rendendo ancora più difficile il debug.
Il problema del debug di questi spuri ClassCastExceptionspuò essere alleviato con la Collections#checkedCollectionfamiglia di metodi.
Un modo più sicuro per la conversione da a List<Supertype> a List<Subtype>è in realtà filtrare l'elenco e creare un nuovo elenco che contenga solo elementi di un determinato tipo. Vi sono alcuni gradi di libertà per l'implementazione di un tale metodo (ad esempio per quanto riguarda il trattamento delle nullvoci), ma una possibile implementazione può apparire così:
/**
* Filter the given list, and create a new list that only contains
* the elements that are (subtypes) of the class c
*
* @param listA The input list
* @param c The class to filter for
* @return The filtered list
*/
private static <T> List<T> filter(List<?> listA, Class<T> c)
{
List<T> listB = new ArrayList<T>();
for (Object a : listA)
{
if (c.isInstance(a))
{
listB.add(c.cast(a));
}
}
return listB;
}
Questo metodo può essere utilizzato per filtrare elenchi arbitrari (non solo con una data relazione Sottotipo-Supertipo relativa ai parametri del tipo), come in questo esempio:
// A list of type "List<Number>" that actually
// contains Integer, Double and Float values
List<Number> mixedNumbers =
new ArrayList<Number>(Arrays.asList(12, 3.4, 5.6f, 78));
// Filter the list, and create a list that contains
// only the Integer values:
List<Integer> integers = filter(mixedNumbers, Integer.class);
System.out.println(integers); // Prints [12, 78]
Non puoi lanciare List<TestB>a List<TestA>come cita Steve Kuo ma è possibile scaricare il contenuto di List<TestA>in List<TestB>. Prova quanto segue:
List<TestA> result = new List<TestA>();
List<TestB> data = new List<TestB>();
result.addAll(data);
Non ho provato questo codice, quindi probabilmente ci sono errori, ma l'idea è che dovrebbe iterare attraverso l'oggetto dati aggiungendo gli elementi (oggetti TestB) all'Elenco. Spero che funzioni per te.
Il modo più sicuro è implementare AbstractListe lanciare oggetti in fase di implementazione. Ho creato la ListUtilclasse helper:
public class ListUtil
{
public static <TCastTo, TCastFrom extends TCastTo> List<TCastTo> convert(final List<TCastFrom> list)
{
return new AbstractList<TCastTo>() {
@Override
public TCastTo get(int i)
{
return list.get(i);
}
@Override
public int size()
{
return list.size();
}
};
}
public static <TCastTo, TCastFrom> List<TCastTo> cast(final List<TCastFrom> list)
{
return new AbstractList<TCastTo>() {
@Override
public TCastTo get(int i)
{
return (TCastTo)list.get(i);
}
@Override
public int size()
{
return list.size();
}
};
}
}
Puoi usare il castmetodo per lanciare alla cieca oggetti nell'elenco e il convertmetodo per lanciare in sicurezza. Esempio:
void test(List<TestA> listA, List<TestB> listB)
{
List<TestB> castedB = ListUtil.cast(listA); // all items are blindly casted
List<TestB> convertedB = ListUtil.<TestB, TestA>convert(listA); // wrong cause TestA does not extend TestB
List<TestA> convertedA = ListUtil.<TestA, TestB>convert(listB); // OK all items are safely casted
}
L'unico modo che conosco è copiando:
List<TestB> list = new ArrayList<TestB> (
Arrays.asList (
testAList.toArray(new TestB[0])
)
);
Quando lanci un riferimento a un oggetto, stai semplicemente trasmettendo il tipo di riferimento, non il tipo di oggetto. il casting non cambierà il tipo effettivo dell'oggetto.
Java non ha regole implicite per la conversione dei tipi di oggetto. (A differenza dei primitivi)
Invece è necessario fornire come convertire un tipo in un altro e chiamarlo manualmente.
public class TestA {}
public class TestB extends TestA{
TestB(TestA testA) {
// build a TestB from a TestA
}
}
List<TestA> result = ....
List<TestB> data = new List<TestB>();
for(TestA testA : result) {
data.add(new TestB(testA));
}
Questo è più dettagliato che in una lingua con supporto diretto, ma funziona e non dovresti farlo molto spesso.
se hai un oggetto della classe TestA, non puoi lanciarlo TestB. ogni TestBè unTestA , ma non viceversa.
nel seguente codice:
TestA a = new TestA();
TestB b = (TestB) a;
la seconda linea lancerebbe a ClassCastException .
puoi lanciare un TestAriferimento solo se l'oggetto stesso è TestB. per esempio:
TestA a = new TestB();
TestB b = (TestB) a;
quindi, potresti non sempre trasmettere un elenco TestAa un elenco di TestB.
È possibile utilizzare il selectInstancesmetodo nelle raccolte Eclipse . Ciò ha comportato la creazione di una nuova collezione, tuttavia non sarà efficiente come la soluzione accettata che utilizza il casting.
List<CharSequence> parent =
Arrays.asList("1","2","3", new StringBuffer("4"));
List<String> strings =
Lists.adapt(parent).selectInstancesOf(String.class);
Assert.assertEquals(Arrays.asList("1","2","3"), strings);
Ho incluso StringBuffernell'esempio per dimostrarloselectInstances non solo esegue il downcast del tipo, ma filtrerà anche se la raccolta contiene tipi misti.
Nota: sono un committer per le raccolte Eclipse.
Ciò è possibile a causa della cancellazione del tipo. Lo troverai
List<TestA> x = new ArrayList<TestA>();
List<TestB> y = new ArrayList<TestB>();
x.getClass().equals(y.getClass()); // true
Internamente entrambi gli elenchi sono di tipo List<Object>. Per questo motivo non puoi lanciare l'uno sull'altro: non c'è nulla da lanciare.
Integer j = 42; Integer i = (Integer) j;funziona bene, entrambi sono numeri interi, entrambi hanno la stessa classe, quindi "non c'è nulla da trasmettere".
Il problema è che il tuo metodo NON restituisce un elenco di TestA se contiene un TestB, quindi cosa succede se è stato digitato correttamente? Quindi questo cast:
class TestA{};
class TestB extends TestA{};
List<? extends TestA> listA;
List<TestB> listB = (List<TestB>) listA;
funziona così come potresti sperare (Eclipse ti avverte di un cast non controllato che è esattamente quello che stai facendo, quindi meh). Quindi puoi usarlo per risolvere il tuo problema? In realtà è possibile per questo motivo:
List<TestA> badlist = null; // Actually contains TestBs, as specified
List<? extends TestA> talist = badlist; // Umm, works
List<TextB> tblist = (List<TestB>)talist; // TADA!
Esattamente quello che hai chiesto, giusto? o per essere veramente esatti:
List<TestB> tblist = (List<TestB>)(List<? extends TestA>) badlist;
sembra compilarsi bene per me.
class MyClass {
String field;
MyClass(String field) {
this.field = field;
}
}
@Test
public void testTypeCast() {
List<Object> objectList = Arrays.asList(new MyClass("1"), new MyClass("2"));
Class<MyClass> clazz = MyClass.class;
List<MyClass> myClassList = objectList.stream()
.map(clazz::cast)
.collect(Collectors.toList());
assertEquals(objectList.size(), myClassList.size());
assertEquals(objectList, myClassList);
}
Questo test dimostra come lanciare List<Object>a List<MyClass>. Ma è necessario prestare attenzione a che objectListdeve contenere istanze dello stesso tipo di MyClass. E questo esempio può essere considerato quando List<T>viene utilizzato. A questo scopo, ottieni il campo Class<T> clazznel costruttore e usalo al posto di MyClass.class.
Questo dovrebbe funzionare
List<TestA> testAList = new ArrayList<>();
List<TestB> testBList = new ArrayList<>()
testAList.addAll(new ArrayList<>(testBList));
Abbastanza strano che il casting manuale di un elenco non sia ancora fornito da alcuni toolbox che implementano qualcosa del tipo:
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends E, E> List<T> cast(List<E> list) {
return (List) list;
}
Naturalmente, questo non controllerà gli elementi uno per uno, ma questo è esattamente ciò che vogliamo evitare qui, se sappiamo bene che la nostra implementazione fornisce solo il sottotipo.