你如何将一个超类型列表投给一个子类型列表?
例如,假设你有两个类:
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教程中取得的例子
假设有两种类型A
和B
, B extends A
使B extends A
。 那么下面的代码是正确的:
B b = new B(); A a = b;
前面的代码是有效的,因为B
是A
一个子类。 现在, 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));