例如,假设您有两个类:
public class TestA {}
public class TestB extends TestA{}
我有一个返回a的方法,List<TestA>
并且我想将列表中的所有对象都强制转换为,TestB
以便最终得到a List<TestB>
。
例如,假设您有两个类:
public class TestA {}
public class TestB extends TestA{}
我有一个返回a的方法,List<TestA>
并且我想将列表中的所有对象都强制转换为,TestB
以便最终得到a List<TestB>
。
Answers:
简单地铸造List<TestB>
几乎可行;但是它不起作用,因为您不能将一个参数的泛型类型转换为另一个参数。但是,您可以强制转换中间通配符类型,并且将允许它(因为您可以强制转换为通配符类型或从通配符类型强制转换,只是带有未选中的警告):
List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;
无法进行泛型转换,但是如果您以其他方式定义列表,则可以在其中存储TestB:
List<? extends TestA> myList = new ArrayList<TestA>();
使用列表中的对象时,您仍然需要进行类型检查。
您确实不能*:
示例取自该Java教程
假设有两种类型A
,并B
这样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>)b;
List<A> a = (List<A>)t;
但是,您将得到警告。您可以通过添加@SuppressWarnings("unchecked")
到方法中来抑制它。
使用Java 8,您实际上可以
List<TestB> variable = collectionOfListA
.stream()
.map(e -> (TestB) e)
.collect(Collectors.toList());
new List<TestB>
,循环和强制类型转换)吗?
既然这是一个被广泛引用的问题,并且当前的答案主要解释了为什么它不起作用(或者提出了我在生产代码中永远不会看到的骇人,危险的解决方案),所以我认为添加另一个答案是合适的,以示陷阱,以及可能的解决方案。
在其他答案中已经指出了这种方法通常不起作用的原因:转换是否真正有效取决于原始列表中包含的对象的类型。当有其类型不是类型的列表对象TestB
,但不同的子类的TestA
,然后投是不是有效。
当然,强制转换可能是有效的。有时您会获得有关编译器不可用的类型的信息。在这些情况下,可以强制转换列表,但通常不建议这样做:
一个可以...
第一种方法(对应于当前接受的答案)的含义很微妙。乍看起来,它似乎可以正常工作。但是,如果输入列表中有错误的类型,则将ClassCastException
抛出a,可能会在代码中的一个完全不同的位置,并且可能很难调试它并找出错误的元素滑入列表的位置。最严重的问题是,在转换列表之后,有人甚至可能添加无效元素,从而使调试更加困难。
调试这些杂散的问题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;
}
可以使用此方法来过滤任意列表(不仅具有与类型参数有关的给定Subtype-Supertype关系),如本例所示:
// 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对象)添加到List中。希望对您有用。
最好的安全方法是在实施中实施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
}
投射对象引用时,您只是在投射引用的类型,而不是对象的类型。强制转换不会更改对象的实际类型。
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));
}
这比具有直接支持的语言更为冗长,但是它可以正常工作,您不需要经常这样做。
如果您有该类的对象TestA
,则无法将其转换为TestB
。每种方式TestB
都是TestA
,但并非相反。
在以下代码中:
TestA a = new TestA();
TestB b = (TestB) a;
第二行将抛出一个ClassCastException
。
TestA
如果对象本身是,则只能投射参考TestB
。例如:
TestA a = new TestB();
TestB b = (TestB) a;
因此,您不一定总是将的列表TestA
转换为的列表TestB
。
您可以使用Eclipse Collections中的selectInstances
方法。这将涉及创建一个新的集合,但是效率不如使用转换的公认解决方案高。
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);
我StringBuffer
在示例中进行了演示,以表明selectInstances
不仅向下转换类型,而且还可以过滤集合中包含混合类型的类型。
注意:我是Eclipse Collections的提交者。
由于类型擦除,这是可能的。你会发现
List<TestA> x = new ArrayList<TestA>();
List<TestB> y = new ArrayList<TestB>();
x.getClass().equals(y.getClass()); // true
内部两个列表都是类型List<Object>
。出于这个原因,您不能将一个投向另一个-没有要投的东西。
Integer j = 42; Integer i = (Integer) j;
可以正常工作,它们都是整数,它们都具有相同的类,因此“没有强制转换”。
问题在于,如果您的方法包含TestB,则该方法不会返回TestA的列表,那么,如果键入正确,该怎么办?然后这个演员:
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;
似乎对我来说编译就很好。
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
。