Angenommen, Sie haben zwei Klassen:
public class TestA {}
public class TestB extends TestA{}
Ich habe eine Methode, die ein List<TestA>
und ich möchte alle Objekte in dieser Liste in TestB
umwandeln, sodass ich am Ende ein List<TestB>
.
Einfach Casting zu List<TestB>
funktioniert fast; Dies funktioniert jedoch nicht, da Sie einen generischen Typ eines Parameters nicht in einen anderen umwandeln können. Sie können jedoch einen Zwischen-Platzhaltertyp durchgehen, und dieser ist zulässig (da Sie nur mit einer nicht aktivierten Warnung auf Platzhaltertypen und von diesen wechseln können):
List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;
das Casting von Generika ist nicht möglich, aber wenn Sie die Liste auf andere Weise definieren, ist es möglich, TestB darin zu speichern:
List<? extends TestA> myList = new ArrayList<TestA>();
Sie müssen noch die Typüberprüfung durchführen, wenn Sie die Objekte in der Liste verwenden.
Sie können wirklich nicht *:
Das Beispiel stammt aus dieses Java Tutorial
Angenommen, es gibt zwei Typen A
und B
, so dass B extends A
. Dann ist der folgende Code richtig:
B b = new B();
A a = b;
Der vorherige Code ist gültig, da B
eine Unterklasse von A
ist. Was passiert nun mit List<A>
Und List<B>
?
Es stellt sich heraus, dass List<B>
Keine Unterklasse von List<A>
Ist, daher können wir nicht schreiben
List<B> b = new ArrayList<>();
List<A> a = b; // error, List<B> is not of type List<A>
Außerdem können wir nicht einmal schreiben
List<B> b = new ArrayList<>();
List<A> a = (List<A>)b; // error, List<B> is not of type List<A>
*: Um das Casting zu ermöglichen, benötigen wir ein gemeinsames übergeordnetes Element für List<A>
Und List<B>
: List<?>
. Folgendes ist gültig:
List<B> b = new ArrayList<>();
List<?> t = (List<B>)b;
List<A> a = (List<A>)t;
Sie erhalten jedoch eine Warnung. Sie können es unterdrücken, indem Sie Ihrer Methode @SuppressWarnings("unchecked")
hinzufügen.
Mit Java 8 können Sie tatsächlich
List<TestB> variable = collectionOfListA
.stream()
.map(e -> (TestB) e)
.collect(Collectors.toList());
Ich denke, Sie wandeln in die falsche Richtung ... Wenn die Methode eine Liste von TestA
Objekten zurückgibt, ist es wirklich nicht sicher, sie in TestB
zu wandeln.
Grundsätzlich bitten Sie den Compiler, TestB
Operationen auf einem Typ TestA
ausführen zu lassen, der diese nicht unterstützt.
Da es sich um eine häufig gestellte Frage handelt und die aktuellen Antworten hauptsächlich erklären, warum sie nicht funktionieren (oder hackige, gefährliche Lösungen vorschlagen, die ich niemals im Produktionscode sehen möchte), halte ich es für angebracht, eine weitere Antwort hinzuzufügen die Fallstricke und eine mögliche Lösung.
Der Grund, warum dies im Allgemeinen nicht funktioniert, wurde bereits in anderen Antworten erläutert: Ob die Konvertierung tatsächlich gültig ist oder nicht, hängt von den Objekttypen ab, die in der ursprünglichen Liste enthalten sind. Wenn die Liste Objekte enthält, deren Typ nicht vom Typ TestB
ist, sondern von einer anderen Unterklasse von TestA
, ist die Besetzung not gültig.
Natürlich sind die Darstellungen darf gültig. Manchmal haben Sie Informationen zu den Typen, die für den Compiler nicht verfügbar sind. In diesen Fällen ist es möglich, die Listen zu besetzen, aber im Allgemeinen wird nicht empfohlen :
Man könnte entweder ...
Die Implikationen des ersten Ansatzes (der der aktuell akzeptierten Antwort entspricht) sind subtil. Es scheint auf den ersten Blick richtig zu funktionieren. Wenn sich jedoch falsche Typen in der Eingabeliste befinden, wird ein ClassCastException
ausgegeben, möglicherweise an einer völlig anderen Stelle im Code, und es kann schwierig sein, dies zu debuggen und herauszufinden, wo das falsche Element abhanden gekommen ist in die Liste. Das schlimmste Problem ist, dass jemand die ungültigen Elemente hinzufügen könnte after die Liste wurde umgewandelt, was das Debuggen noch schwieriger macht.
Das Problem des Debuggens dieser falschen ClassCastExceptions
kann mit Collections#checkedCollection
Methodenfamilie.
Eine typsichere Methode zum Konvertieren von List<Supertype>
zu einem List<Subtype>
soll eigentlich filtern die Liste und eine neue Liste erstellen, die nur Elemente enthält, die einen bestimmten Typ haben. Es gibt einige Freiheitsgrade für die Implementierung einer solchen Methode (z. B. in Bezug auf die Behandlung von null
Einträgen), aber eine mögliche Implementierung könnte so aussehen:
/**
* 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;
}
Diese Methode kann verwendet werden, um beliebige Listen zu filtern (nicht nur mit einer bestimmten Subtyp-Supertype-Beziehung in Bezug auf die Typparameter), wie im folgenden Beispiel:
// 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]
Sie können List<TestB>
Nicht in List<TestA>
Umwandeln, wie Steve Kuo erwähnt, ABER Sie können den Inhalt von List<TestA>
In List<TestB>
Ablegen. Versuche Folgendes:
List<TestA> result = new List<TestA>();
List<TestB> data = new List<TestB>();
result.addAll(data);
Ich habe diesen Code nicht ausprobiert, daher gibt es wahrscheinlich Fehler, aber die Idee ist, dass er durch das Datenobjekt iteriert werden sollte, indem die Elemente (TestB-Objekte) zur Liste hinzugefügt werden. Ich hoffe das klappt bei dir.
Am sichersten ist es, ein AbstractList
zu implementieren und Elemente in der Implementierung umzusetzen. Ich habe die Hilfsklasse ListUtil
erstellt:
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();
}
};
}
}
Sie können die Methode cast
verwenden, um Objekte in der Liste blind zu gießen, und die Methode convert
, um sie sicher zu gießen. Beispiel:
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
}
Wenn Sie eine Objektreferenz übertragen, übertragen Sie nur den Typ der Referenz, nicht den Typ des Objekts. Durch das Casting wird der tatsächliche Typ des Objekts nicht geändert.
Java hat keine impliziten Regeln für die Konvertierung von Objekttypen. (Im Gegensatz zu Primitiven)
Stattdessen müssen Sie angeben, wie ein Typ in einen anderen konvertiert und manuell aufgerufen werden kann.
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));
}
Dies ist ausführlicher als in einer Sprache mit direkter Unterstützung, aber es funktioniert und Sie sollten dies nicht sehr oft tun müssen.
Der einzige Weg, den ich kenne, ist das Kopieren von:
List<TestB> list = new ArrayList<TestB> (
Arrays.asList (
testAList.toArray(new TestB[0])
)
);
Dies ist aufgrund der Typlöschung möglich. Sie werden das finden
List<TestA> x = new ArrayList<TestA>();
List<TestB> y = new ArrayList<TestB>();
x.getClass().equals(y.getClass()); // true
Intern sind beide Listen vom Typ List<Object>
. Aus diesem Grund kann man nicht aufeinander einwirken - es gibt nichts zu wirken.
wenn Sie ein Objekt der Klasse TestA
haben, können Sie es nicht in TestB
umwandeln. jedes TestB
ist ein TestA
, aber nicht andersherum.
im folgenden Code:
TestA a = new TestA();
TestB b = (TestB) a;
die zweite Zeile würde einen ClassCastException
auslösen.
sie können eine TestA
-Referenz nur setzen, wenn das Objekt selbst TestB
ist. zum Beispiel:
TestA a = new TestB();
TestB b = (TestB) a;
daher können Sie eine Liste von TestA
nicht immer in eine Liste von TestB
umwandeln.
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);
}
Dieser Test zeigt, wie man List<Object>
bis List<MyClass>
. Beachten Sie jedoch, dass objectList
Instanzen desselben Typs wie MyClass
enthalten muss. Und dieses Beispiel kann in Betracht gezogen werden, wenn List<T>
wird genutzt. Zu diesem Zweck erhalten Sie das Feld Class<T> clazz
im Konstruktor und benutze es anstelle von MyClass.class
.
Das Problem ist, dass Ihre Methode KEINE Liste von TestA zurückgibt, wenn sie ein TestB enthält. Was ist, wenn sie korrekt eingegeben wurde? Dann diese Besetzung:
class TestA{};
class TestB extends TestA{};
List<? extends TestA> listA;
List<TestB> listB = (List<TestB>) listA;
funktioniert ungefähr so gut, wie Sie es sich erhoffen könnten (Eclipse warnt Sie vor einer ungeprüften Besetzung, die genau das ist, was Sie tun, also meh). Können Sie dies also verwenden, um Ihr Problem zu lösen? Eigentlich können Sie aus diesem Grund:
List<TestA> badlist = null; // Actually contains TestBs, as specified
List<? extends TestA> talist = badlist; // Umm, works
List<TextB> tblist = (List<TestB>)talist; // TADA!
Genau das, wonach Sie gefragt haben, richtig? oder um genau zu sein:
List<TestB> tblist = (List<TestB>)(List<? extends TestA>) badlist;
scheint ganz gut für mich zu kompilieren.
Sie können die selectInstances
-Methode in Eclipse Collections verwenden. Dies erfordert die Erstellung einer neuen Sammlung, ist jedoch nicht so effizient wie die akzeptierte Lösung, bei der Casting verwendet wird.
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);
Ich habe StringBuffer
in das Beispiel aufgenommen, um zu zeigen, dass selectInstances
nicht nur den Typ herunterwirft, sondern auch filtert, wenn die Sammlung gemischte Typen enthält.
Hinweis: Ich bin ein Committer für Eclipse-Sammlungen.
Das sollte funktionieren
List<TestA> testAList = new ArrayList<>();
List<TestB> testBList = new ArrayList<>()
testAList.addAll(new ArrayList<>(testBList));