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

Jer*_*gan 231 java generics collections casting list

例如,假设您有两个类:

public class TestA {}
public class TestB extends TestA{}
Run Code Online (Sandbox Code Playgroud)

我有一个返回a的方法,List<TestA>我想将该列表中的所有对象强制转换为TestB最终得到一个List<TestB>.

new*_*cct 560

简单地施展到List<TestB>近乎工作; 但它不起作用,因为你不能将一个参数的泛型类型转换为另一个参数.但是,您可以通过中间通配符类型进行转换,并且允许它(因为您可以使用未选中的警告来转换为通配符类型和通配符类型):

List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;
Run Code Online (Sandbox Code Playgroud)

  • 尽管如此,我认为这是一种hacky解决方案 - 你正在躲避Java试图为你提供的类型安全性.至少请看一下这个Java教程(http://docs.oracle.com/javase/tutorial/java/generics/subtyping.html),并在以这种方式修复之前考虑为什么会遇到这个问题. (85认同)
  • @ jfritz42我不会称之为"躲避"式安全.在这种情况下,您知道Java没有.这就是铸造的目的. (56认同)
  • 这让我这样写:List &lt;String&gt; myList =(List &lt;String&gt;)(List &lt;?&gt;)(new ArrayList &lt;Integer&gt;()); 这将在运行时崩溃。我更喜欢List &lt;?扩展Number&gt; myList = new ArrayList &lt;Integer&gt;(); (3认同)
  • 老实说,我同意 @jfritz42 的观点,我遇到了同样的问题,查看了他提供的 URL,并且能够在不进行转换的情况下解决我的问题。 (2认同)

Sal*_*dur 113

无法转换泛型,但如果以另一种方式定义列表,则可以在其中存储TestB:

List<? extends TestA> myList = new ArrayList<TestA>();
Run Code Online (Sandbox Code Playgroud)

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

  • 这甚至不是有效的Java语法. (11认同)
  • 你能给出一个[MCVE](/ help/mcve)吗?我得到`无法实例化类型ArrayList <?扩展了TestA>`. (7认同)
  • 没错,但这比事实更具有说明性 (2认同)
  • 如果TestA是界面怎么办? (2认同)

Ste*_*Kuo 41

你真的不能*:

示例来自此Java教程

假设有两种类型A,B等等B extends A.那么下面的代码是正确的:

    B b = new B();
    A a = b;
Run Code Online (Sandbox Code Playgroud)

之前的代码是有效的,因为它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>
Run Code Online (Sandbox Code Playgroud)

而且,我们甚至不能

    List<B> b = new ArrayList<>();
    List<A> a = (List<A>)b; // error, List<B> is not of type List<A>
Run Code Online (Sandbox Code Playgroud)

*:为了使铸造可能我们需要双方共同的父List<A>List<B>:List<?>例如.以下内容有效:

    List<B> b = new ArrayList<>();
    List<?> t = (List<B>)b;
    List<A> a = (List<A>)t;
Run Code Online (Sandbox Code Playgroud)

但是,您会收到警告.您可以通过添加@SuppressWarnings("unchecked")到您的方法来抑制它.

  • 此外,使用此链接中的材料可以避免未经检查的转换,因为编译器可以解密整个转换. (2认同)

fra*_*acz 28

使用Java 8,您实际上可以

List<TestB> variable = collectionOfListA
    .stream()
    .map(e -> (TestB) e)
    .collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

  • 这实际上是在列出名单吗?因为它更像是一系列操作来迭代整个列表,所以转换每个元素,然后将它们添加到所需类型的新实例化列表中.换句话说,难道你不能用Java7做这个 - 新的List <TestB>`,一个循环和一个演员吗? (30认同)
  • 从OP中“我想转换列表中的所有对象”-因此,实际上,这是回答上述问题的少数答案之一,即使这不是人们在这里浏览的问题。 (2认同)

jer*_*jvl 18

我认为你正朝着错误的方向投射......如果该方法返回一个TestA对象列表,那么将它们强制转换是不安全的TestB.

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


Mar*_*o13 9

由于这是一个广泛引用的问题,目前的答案主要解释为什么它不起作用(或提出我从未想过在生产代码中看到的hacky,危险的解决方案),我认为添加另一个答案是合适的,显示陷阱,以及可能的解决方案.


一般不起作用的原因已在其他答案中指出:转换是否实际有效取决于原始列表中包含的对象的类型.当有其类型不是类型的列表对象TestB,但不同的子类的TestA,然后投是不是有效.


当然,演员阵容可能有效.您有时会获得有关编译器不可用的类型的信息.在这些情况下,可以转换列表,但一般情况下,不建议:

一个可以......

  • ...投下整个清单或
  • ...投射列表的所有元素

第一种方法(对应于当前接受的答案)的含义是微妙的.乍一看似乎工作正常.但是如果输入列表中存在错误的类型,那么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;
}
Run Code Online (Sandbox Code Playgroud)

此方法可用于过滤任意列表(不仅具有关于类型参数的给定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]
Run Code Online (Sandbox Code Playgroud)


mar*_*ime 6

你可以不投List<TestB>List<TestA>史蒂夫·郭提及,但你可以转储的内容List<TestA>List<TestB>.请尝试以下方法:

List<TestA> result = new List<TestA>();
List<TestB> data = new List<TestB>();
result.addAll(data);
Run Code Online (Sandbox Code Playgroud)

我没有尝试过这段代码,所以可能存在错误,但想法是它应该迭代数据对象,将元素(TestB对象)添加到List中.我希望这对你有用.

  • 当然这篇文章并不正确.但提问的人最终需要一份TestB清单.在这种情况下,这个答案是误导性的.您可以通过调用List <TestA> .addAll(List <TestB>)将List <TestB>中的所有元素添加到List <TestA>.但是你不能使用List <TestB> .addAll(List <TestA>); (7认同)

Igo*_*nok 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();
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以使用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
}
Run Code Online (Sandbox Code Playgroud)


小智 5

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

List<TestB> list = new ArrayList<TestB> (
    Arrays.asList (
        testAList.toArray(new TestB[0])
    )
);
Run Code Online (Sandbox Code Playgroud)

  • 很奇怪,我认为这不会给出编译器警告。 (2认同)

Mik*_*kis 5

2022 年答复

将超类型列表转换为子类型列表是无意义的,没有人应该尝试甚至考虑做这样的事情。如果您认为您的代码需要执行此操作,则需要重写您的代码,使其不需要执行此操作。

这个问题的大多数访问者可能想做相反的事情,这实际上是有道理的:

将子类型列表转换为超类型列表。

我发现的最好的方法如下:

List<TestA> testAs = List.copyOf( testBs );
Run Code Online (Sandbox Code Playgroud)

这样做有以下优点:

  • 这是一篇简洁的单行诗
  • 它不会产生任何警告
  • 如果您的列表是使用以下命令创建的,则它不会复制List.of()!!!
  • 最重要的是:它做了正确的事。

为什么这是正确的事情?

如果你查看源代码,List.copyOf()你会发现它的工作原理如下:

  • 如果您的列表是使用以下命令创建的List.of(),那么它将进行转换并返回它而不复制它。
  • 否则,(例如,如果您的列表是一个ArrayList(),)它将创建一个副本并返回它。

如果您List<TestB>是,则必须制作ArrayList<TestB>一份副本。如果你要转换as ,你将有可能无意中将 a 添加到 that 中,这会导致你的原始数据在 s中包含 a ,这是内存损坏:尝试迭代原始数据中的所有 s会扔一个ArrayList ArrayList<TestB>List<TestA>TestAList<TestA>ArrayList<TestB>TestATestBTestBArrayList<TestB>ClassCastException.

另一方面,如果 yourList<TestB>是使用 创建的List.of(),那么它是不可更改的(*1),因此没有人可以无意中TestA向其添加 a ,因此可以将其强制转换为List<TestA>


(*1) 当这些列表首次引入时,它们被称为“不可变的”;后来他们意识到称它们为不可变是错误的,因为集合不可能是不可变的,因为它不能保证其包含的元素的不可变性;因此他们更改了文档,将其称为“不可修改”;然而,“不可修改”在这些列表被引入之前就已经有了意义,它意味着“我的列表的对来说是不可修改的,我仍然可以随意改变,并且这些改变对你来说是非常明显的”。因此,不可变或不可修改都​​是不正确的。我喜欢称它们为“表面上不可变”,因为它们并不是深度上不可变的,但这可能会激怒一些人,所以作为妥协,我只是称它们为“不变”。