你如何将一个超类型列表投给一个子类型列表?

例如,假设你有两个类:

public class TestA {} public class TestB extends TestA{} 

我有一个返回一个List<TestA>的方法,我想将该列表中的所有对象都转换为TestB以便最终得到一个List<TestB>

简单地转换为List<TestB>几乎可以工作; 但它不起作用,因为你不能将一个泛型的参数转换为另一个参数。 但是,您可以通过中间通配符类型进行强制转换,因此可以执行此操作(因为您可以使用通配符类型进行强制转换,只需使用未经检查的警告即可):

 List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA; 

泛型的转换是不可能的,但是如果以另一种方式定义列表,可以在其中存储TestB:

 List<? extends TestA> myList = new ArrayList<? extends TestA>(); 

在列表中使用对象时,仍然需要执行类型检查。

你真的不能*:

从这个Java教程中取得的例子

假设有两种类型ABB extends A使B extends A 。 那么下面的代码是正确的:

  B b = new B(); A a = b; 

前面的代码是有效的,因为BA一个子类。 现在, List<A>List<B>会发生什么?

事实证明, List<B>不是List<A>的子类,所以我们不能

  List<B> b = new ArrayList<>(); List<A> a = b; // error, List<B> is not of type List<A> 

而且,我们甚至不能

  List<B> b = new ArrayList<>(); List<A> a = (List<A>)b; // error, List<B> is not of type List<A> 

*:为了使可能的转换成为可能,我们需要一个共同的父级,例如List<A>List<B>List<?> 。 以下有效的:

  List<B> b = new ArrayList<>(); List<?> t = (List<B>); List<A> a = (List<A>)t; 

您将会收到警告。 您可以通过向您的方法添加@SuppressWarnings("unchecked")来抑制它。

用Java 8,你实际上可以

 List<TestB> variable = collectionOfListA .stream() .map(e -> (TestB) e) .collect(Collectors.toList()); 

我认为你正在朝着错误的方向投射……如果方法返回一个TestA对象的列表,那么将它们投射到TestB真的不安全。

基本上你要求编译器让你在一个不支持它们的类型TestA上执行TestB操作。

最安全的方法是在实现中实现AbstractList并投射项目。 我创建了ListUtil帮助器类:

 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(); } }; } } 

您可以使用cast方法在列表中盲目投射对象并convert安全投射的方法。 例:

 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 } 

由于这是一个被广泛引用的问题,目前的答案主要解释了为什么它不起作用(或者提出我从来不想在生产代码中看到的危险的解决方案),我认为增加另一个答案是恰当的,陷阱和一个可能的解决方案。


其他答案中已经指出了这一般不起作用的原因:转换是否实际有效取决于原始列表中包含的对象的类型。 当列表中的类型不是TestB类型,而是TestA的不同子类的TestA ,则TestA无效。


当然,演员可能是有效的。 您有时会得到有关编译器不可用的类型的信息。 在这些情况下,可以列出这些列表,但一般情况下不建议

人们可以…

  • …整个列表或…
  • …列表中的所有元素

第一种方法(相当于目前接受的答案)的含义是微妙的。 乍看起来似乎可以正常工作。 但是,如果在输入列表中有错误的类型,那么ClassCastException一个ClassCastException异常,可能在代码中的一个完全不同的位置,并且可能很难调试它,并找出错误元素滑入列表的位置。 最糟糕的问题是,有人甚至可能在列表输出添加无效元素,使调试更加困难。

调试这些错误的ClassCastExceptions的问题可以通过Collections#checkedCollection系列方法来缓解。


根据类型过滤列表

List<Supertype>转换为List<Subtype>的更安全的方法是实际过滤列表,并创建一个仅包含具有特定类型的元素的新列表。 实现这种方法有一定的自由度(例如关于null条目的处理),但是一个可能的实现可能是这样的:

 /** * 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; } 

可以使用此方法来过滤任意列表(不仅在类型参数中使用给定的子类型 – 超类型关系),如下例所示:

 // 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] 

你不能把List<TestB>List<TestA>因为Steve Kuo提到了,但是你可以将List<TestA>的内容转储到List<TestB> 。 尝试以下操作:

 List<TestA> result = new List<TestA>(); List<TestB> data = new List<TestB>(); result.addAll(data); 

我还没有尝试过这个代码,所以可能会有错误,但它的思路是它应该迭代通过数据对象添加元素(TestB对象)到列表中。 我希望这对你有用。

当您投射对象引用时,您只是投射引用的类型,而不是对象的类型。 铸造不会改变对象的实际类型。

Java没有用于转换对象类型的隐式规则。 (与基元不同)

相反,您需要提供如何将一种类型转换为另一种类型并手动调用。

 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)); } 

这比直接支持的语言更加冗长,但是它可以工作,而且你不需要经常这样做。

我知道的唯一方法是复制:

 List<TestB> list = new ArrayList<TestB> ( Arrays.asList ( testAList.toArray(new TestB[0]) ) ); 

由于类型删除,这是可能的。 你会发现的

 List<TestA> x = new ArrayList<TestA>(); List<TestB> y = new ArrayList<TestB>(); x.getClass().equals(y.getClass()); // true 

内部这两个列表的类型是List<Object> 。 因为这个原因,你不能把一个投向另一个 – 没有什么可投的。

如果您有TestA类的对象,则不能将其转换为TestB 。 每个TestB都是一个TestA ,但不是其他方式。

在下面的代码中:

 TestA a = new TestA(); TestB b = (TestB) a; 

第二行会抛出一个ClassCastException

如果对象本身是TestB则只能投射TestA参考。 例如:

 TestA a = new TestB(); TestB b = (TestB) a; 

所以,你可能并不总是把TestA的列表投给TestB列表。

问题是你的方法不会返回一个TestA的列表,如果它包含一个TestB,那么如果它是正确的类型呢? 那么这个演员:

 class TestA{}; class TestB extends TestA{}; List<? extends TestA> listA; List<TestB> listB = (List<TestB>) listA; 

和你所希望的一样(Eclipse会警告你一个没有经过检查的演员,这正是你正在做的,所以就是这样)。 那么你能用这个来解决你的问题吗? 其实你可以这样做:

 List<TestA> badlist = null; // Actually contains TestBs, as specified List<? extends TestA> talist = badlist; // Umm, works List<TextB> tblist = (List<TestB>)talist; // TADA! 

到底你在问什么,对吗? 或者确切地说:

 List<TestB> tblist = (List<TestB>)(List<? extends TestA>) badlist; 

似乎为我编译得很好。

这应该会工作

 List<TestA> testAList = new ArrayList<>(); List<TestB> testBList = new ArrayList<>() testAList.addAll(new ArrayList<>(testBList));