您如何将超类型列表转换为子类型列表?


Answers:


578

简单地铸造List<TestB>几乎可行;但是它不起作用,因为您不能将一个参数的泛型类型转换为另一个参数。但是,您可以强制转换中间通配符类型,并且将允许它(因为您可以强制转换为通配符类型或从通配符类型强制转换,只是带有未选中的警告):

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

88
出于所有应有的尊重,我认为这是一种不可靠的解决方案-您正在避开Java试图为您提供的安全类型。至少请看一下此Java教程(docs.oracle.com/javase/tutorial/java/generics/subtyping.html),并考虑在解决此问题之前为什么会遇到此问题。
jfritz42,2012年

63
@ jfritz42我不会称其为“躲避”类型的安全性。在这种情况下,您将了解Java没有的知识。那就是铸造的目的。
Planky

3
这让我这样写:List <String> myList =(List <String>)(List <?>)(new ArrayList <Integer>()); 这将在运行时崩溃。我更喜欢List <?扩展Number> myList = new ArrayList <Integer>();
Bentaye

1
老实说,我同意@ jfritz42,我遇到了同样的问题,并查看了他提供的URL,能够在不进行强制转换的情况下解决我的问题。
山姆Orozco

@ jfritz42,此功能与将对象强制转换为整数(编译器允许)没有什么不同
kcazllerraf

116

无法进行泛型转换,但是如果您以其他方式定义列表,则可以在其中存储TestB:

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

使用列表中的对象时,您仍然需要进行类型检查。


14
这甚至不是有效的Java语法。
newacct 2013年

2
没错,但事实证明它比事实更正确
Salandur

我有以下方法:createSingleString(List <?extended Object> objects)在此方法中,我调用String.valueOf(Object)将列表折叠成一个字符串。当输入为List <Long>,List <Boolean>等时,它的效果很好。谢谢!
克里斯·斯普拉格

2
如果TestA是接口怎么办?
Suvitruf-Andrei Apanasik

8
您可以在实际可行的地方提供MCVE吗?我懂了Cannot instantiate the type ArrayList<? extends TestA>
Nateowami 2015年

42

您确实不能*:

示例取自该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")到方法中来抑制它。


2
同样,在此链接中使用素材可以避免未经检查的转换,因为编译器可以解密整个转换。
瑞安H

29

使用Java 8,您实际上可以

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

32
那真的是名单吗?因为它读起来更像一系列操作,以遍历整个列表,转换每个元素,然后将它们添加到所需类型的新实例化列表中。换句话说,您不能使用Java7(带有new List<TestB>,循环和强制类型转换)吗?
Patrick M

5
从OP中“我想转换列表中的所有对象”-因此,实际上,这是回答上述问题的少数答案之一,即使这不是人们在这里浏览的问题。
路加·乌瑟伍德

17

我认为您的投放方向错误了……如果该方法返回一个TestA对象列表,那么将其投射到确实是不安全的TestB

基本上,您是在要求编译器允许您TestBTestA不支持它们的类型执行操作。


11

既然这是一个被广泛引用的问题,并且当前的答案主要解释了为什么它不起作用(或者提出了我在生产代码中永远不会看到的骇人,危险的解决方案),所以我认为添加另一个答案是合适的,以示陷阱,以及可能的解决方案。


在其他答案中已经指出了这种方法通常不起作用的原因:转换是否真正有效取决于原始列表中包含的对象的类型。当有其类型不是类型的列表对象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]

7

您无法投射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中。希望对您有用。


为什么这次被否决?这对我很有用,我看不到有关否决投票的任何解释。
zod 2011年

7
当然,这个帖子是不正确的。但是提出这个问题的人最终需要TestB的列表。在那种情况下,这个答案是令人误解的。您可以通过调用List <TestA> .addAll(List <TestB>)将List <TestB>中的所有元素添加到List <TestA>中。但是您不能使用List <TestB> .addAll(List <TestA>);
prageeth

6

最好的安全方法是在实施中实施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
}

4

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

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

2
我认为这并不给编译器警告,这很奇怪。
西蒙·佛斯伯格

这与数组很奇怪。感叹,java数组是如此无用。但是,我认为这肯定不会比其他答案差,而且可读性强。我没有看到比演员更不安全的事情了。
Thufir

3

投射对象引用时,您只是在投射引用的类型,而不是对象的类型。强制转换不会更改对象的实际类型。

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

这比具有直接支持的语言更为冗长,但是它可以正常工作,您不需要经常这样做。


2

如果您有该类的对象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


1
我认为他在谈论类似的事情,您将方法参数“ a”创建为-TestA a = new TestB(),然后要获取TestB对象,然后需要将其强制转换为-TestB b =( TestB)a;
samsamara

2

您可以使用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的提交者。


1

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

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;可以正常工作,它们都是整数,它们都具有相同的类,因此“没有强制转换”。
西蒙·福斯伯格

1

问题在于,如果您的方法包含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;

似乎对我来说编译就很好。


1
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


0

这应该工作

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

1
这将形成不必要的副本。
defnull

0

奇怪的是,实现以下内容的某些工具箱仍未提供手动转换列表:

@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends E, E> List<T> cast(List<E> list) {
    return (List) list;
}

当然,这不会一一检查项目,但这正是我们要避免的事情,如果我们知道我们的实现仅提供子类型。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.