How do you cast a List of supertypes to a List of subtypes?
例如,假设您有两个类:
1 2 | public class TestA {} public class TestB extends TestA{} |
我有一个返回
简单地强制转换到
1 | List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA; |
泛型的强制转换是不可能的,但是如果您以另一种方式定义列表,则可以在其中存储testb:
1 | List<? extends TestA> myList = new ArrayList<TestA>(); |
在使用列表中的对象时,仍然需要进行类型检查。
你真的不能:
从Java教程中获取示例
假设有两种类型的
1 2 | B b = new B(); A a = b; |
前面的代码是有效的,因为
1 2 |
而且,我们甚至不能写
1 2 |
*:为了使铸造成为可能,我们需要一个
不过,你会得到一个警告。您可以通过在方法中添加
用Java 8,你实际上可以
1 2 3 4 | List<TestB> variable = collectionOfListA .stream() .map(e -> (TestB) e) .collect(Collectors.toList()); |
我觉得你选错了方向…如果该方法返回
基本上,您要求编译器允许您对不支持它们的类型
由于这是一个广泛引用的问题,当前的答案主要解释了为什么它不起作用(或提出我永远不想在生产代码中看到的黑客、危险的解决方案),我认为添加另一个答案、显示陷阱和可能的解决方案是合适的。
一般来说,这不起作用的原因已经在其他答案中指出:转换是否实际有效取决于原始列表中包含的对象类型。如果列表中有对象的类型不是
当然,这些石膏可能是有效的。有时您会得到关于编译器不可用类型的信息。在这些情况下,可以铸造列表,但一般来说,不建议:
一个人可以…
- …铸造整个清单或
- …强制转换列表中的所有元素
第一种方法(对应于当前接受的答案)的含义是微妙的。乍一看,它似乎工作正常。但是,如果输入列表中有错误的类型,那么将抛出一个
这些伪
从
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /** * 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; } |
此方法可用于筛选任意列表(不仅具有与类型参数相关的给定子类型超类型关系),如本例中所示:
1 2 3 4 5 6 7 8 9 10 | // 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] |
你不能像Steve Kuo提到的那样将
1 2 3 | List<TestA> result = new List<TestA>(); List<TestB> data = new List<TestB>(); result.addAll(data); |
我没有尝试过这段代码,所以可能会有错误,但我的想法是它应该迭代数据对象,将元素(testb对象)添加到列表中。我希望这对你有用。
最好的安全方法是在实现中实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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(); } }; } } |
您可以使用
1 2 3 4 5 6 | 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 } |
我唯一知道的方法是复制:
1 2 3 4 5 |
当您转换一个对象引用时,您只是转换引用的类型,而不是对象的类型。强制转换不会更改对象的实际类型。
Java不具有用于转换对象类型的隐式规则。(与原语不同)
相反,您需要提供如何将一种类型转换为另一种类型并手动调用它。
1 2 3 4 5 6 7 8 9 10 11 12 | 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)); } |
这比直接支持的语言更冗长,但它是有效的,您不需要经常这样做。
如果有
在以下代码中:
1 2 | TestA a = new TestA(); TestB b = (TestB) a; |
第二行将抛出一个
如果对象本身是
1 2 | TestA a = new TestB(); TestB b = (TestB) a; |
因此,您可能不会总是将EDOCX1的列表(13)强制转换为EDOCX1的列表(14)。
由于类型擦除,这是可能的。你会发现的
1 2 3 | List<TestA> x = new ArrayList<TestA>(); List<TestB> y = new ArrayList<TestB>(); x.getClass().equals(y.getClass()); // true |
在内部,这两个列表都是
您可以在Eclipse集合中使用
1 2 3 4 5 | 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); |
我在示例中加入了
注意:我是Eclipse集合的提交者。
问题是,如果您的方法包含一个testb,那么它不会返回一个testa列表,那么如果它是正确键入的呢?然后这个演员:
1 2 3 4 | class TestA{}; class TestB extends TestA{}; List<? extends TestA> listA; List<TestB> listB = (List<TestB>) listA; |
工作和你希望的一样好(Eclipse警告你一个未检查的演员表,这正是你所做的,所以Meh)。你能用这个来解决你的问题吗?实际上,你可以这样做:
1 2 3 | List<TestA> badlist = null; // Actually contains TestBs, as specified List<? extends TestA> talist = badlist; // Umm, works List<TextB> tblist = (List<TestB>)talist; // TADA! |
你到底要什么,对吗?或者更确切地说:
1 | List<TestB> tblist = (List<TestB>)(List<? extends TestA>) badlist; |
似乎对我来说编译得很好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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); } |
这个测试演示了如何将
这应该管用
1 2 3 | List<TestA> testAList = new ArrayList<>(); List<TestB> testBList = new ArrayList<>() testAList.addAll(new ArrayList<>(testBList)); |