例如,假设你有两个类:
public class TestA {}
public class TestB extends TestA{}
我有一个返回List<TestB>的方法,我想将该列表中的所有对象强制转换为TestB,以便最终得到List<TestB>。
例如,假设你有两个类:
public class TestA {}
public class TestB extends TestA{}
我有一个返回List<TestB>的方法,我想将该列表中的所有对象强制转换为TestB,以便最终得到List<TestB>。
当前回答
泛型的强制转换是不可能的,但是如果你以另一种方式定义列表,则可以将TestB存储在其中:
List<? extends TestA> myList = new ArrayList<TestA>();
在使用列表中的对象时,仍然需要进行类型检查。
其他回答
由于这是一个被广泛引用的问题,目前的答案主要解释了为什么它不起作用(或者提出了我永远不希望在生产代码中看到的俗套、危险的解决方案),我认为应该添加另一个答案,展示陷阱和可能的解决方案。
在其他回答中已经指出了一般情况下这不能工作的原因:转换是否实际有效取决于原始列表中包含的对象的类型。当列表中有对象的类型不是TestB类型,而是TestA的不同子类时,则强制转换无效。
当然,类型转换可能是有效的。有时,您拥有编译器无法使用的类型信息。在这些情况下,可以强制转换列表,但通常不建议这样做:
一个人可以……
... 转换整个列表或 ... 强制转换列表中的所有元素
The implications of the first approach (which corresponds to the currently accepted answer) are subtle. It might seem to work properly at the first glance. But if there are wrong types in the input list, then a ClassCastException will be thrown, maybe at a completely different location in the code, and it may be hard to debug this and to find out where the wrong element slipped into the list. The worst problem is that someone might even add the invalid elements after the list has been casted, making debugging even more difficult.
调试这些虚假classcastexception的问题可以通过Collections#checkedCollection系列方法缓解。
基于类型筛选列表
从List<Supertype>转换到List<Subtype>的一种更类型安全的方法是实际筛选列表,并创建一个只包含具有特定类型的元素的新列表。这种方法的实现有一定程度的自由度(例如,关于空项的处理),但一种可能的实现可能是这样的:
/**
* 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]
你真的不能*:
示例取自本Java教程
假设有两种类型A和B,使得B扩展了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>
*:为了使casting成为可能,我们需要一个共同的父List< a >和List<B>: List<?例如>。以下是有效的:
List<B> b = new ArrayList<>();
List<?> t = (List<B>)b;
List<A> a = (List<A>)t;
但是,您将得到一个警告。你可以通过在你的方法中添加@SuppressWarnings("unchecked")来抑制它。
2022年回答
将一组超类型的List转换为一组子类型的List是没有意义的,任何人都不应该尝试或甚至考虑这样做。如果你认为你的代码需要这样做,你需要重写你的代码,使它不需要这样做。
对于这个问题,大多数访问者可能想要做相反的事情,这实际上是有道理的:
将子类型列表转换为超类型列表。
我发现的最好的方法是:
List<TestA> testAs = List.copyOf( testBs );
这样做有以下优点:
这是一个简洁的一行代码 它不会产生警告 如果你的列表是用list .of()创建的,它不会复制!! 最重要的是:它做了正确的事情。
为什么这是对的?
如果你看一下List.copyOf()的源代码,你会发现它的工作原理如下:
如果您的列表是用list .of()创建的,那么它将执行强制转换并返回它,而不复制它。 否则,(例如,如果你的列表是一个ArrayList(),)它将创建一个副本并返回它。
如果List<TestB>是一个<TestB>的数组列表,那么必须创建一个数组列表的副本。如果您将ArrayList<TestB>转换为List<TestA>,则可能会无意中将一个TestA添加到List<TestA>中,这将导致原始ArrayList<TestB>在TestB中包含一个TestA,这是内存破坏:试图迭代原始ArrayList<TestB>中的所有TestB将抛出ClassCastException。
另一方面,如果List<TestB>是使用List.of()创建的,那么它是不可更改的(*1),因此没有人会无意中向其添加一个TestA,因此可以将其强制转换为List<TestA>。
(*1) when these lists were first introduced they were called "immutable"; later they realized that it is wrong to call them immutable, because a collection cannot be immutable, since it cannot vouch for the immutability of the elements that it contains; so they changed the documentation to call them "unmodifiable" instead; however, "unmodifiable" already had a meaning before these lists were introduced, and it meant "an unmodifiable to you view of my list which I am still free to mutate as I please, and the mutations will be very visible to you". So, neither immutable or unmodifiable is correct. I like to call them "superficially immutable" in the sense that they are not deeply immutable, but that may ruffle some feathers, so I just called them "unchangeable" as a compromise.
由于类型擦除,这是可能的。你会发现
List<TestA> x = new ArrayList<TestA>();
List<TestB> y = new ArrayList<TestB>();
x.getClass().equals(y.getClass()); // true
在内部,两个列表的类型都是List<Object>。出于这个原因,你不能将一个转换为另一个——没有什么可以转换的。
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);
}
这个测试演示了如何将List<Object>转换为List<MyClass>。但是需要注意的是,objectList必须包含与MyClass相同类型的实例。当List<T>使用时,可以考虑这个例子。为此,在构造函数中获取字段Class<T> clazz,并使用它来代替MyClass.class。
更新
实际上,在强类型Java AFAIK中,你不能从超类型转换为子类型。但是可以引入超类型实现的接口。
interface ITest {
}
class TestA implements ITest {
}
class TestB extends TestA {
}
public class Main {
public static void main(String[] args) {
List<ITest> testAList = Arrays.asList(new TestA(), new TestA());
Class<ITest> clazz = ITest.class;
List<ITest> testBList = testAList.stream()
.map(clazz::cast)
.collect(Collectors.toList());
System.out.println(testBList.size());
System.out.println(testAList);
}
}
或者可以从子类型转换为超类型。是这样的:
class TestA {
}
class TestB extends TestA {
}
public class Main {
public static void main(String[] args) {
var a = new TestA();
var b = new TestB();
System.out.println(((TestA) b));
List<TestB> testBList = Arrays.asList(new TestB(), new TestB());
Class<TestA> clazz = TestA.class;
List<TestA> testAList = testBList.stream()
.map(clazz::cast)
.collect(Collectors.toList());
System.out.println(testAList.size());
System.out.println(testAList);
}
}
供参考,我最初的答案指出了与泛型一起使用它的可能性。事实上,我从Hibernate DAO代码中获取了它。