将 List<SubClass> 转换为 List<BaseClass> 的最有效方法

Posted

技术标签:

【中文标题】将 List<SubClass> 转换为 List<BaseClass> 的最有效方法【英文标题】:Most efficient way to cast List<SubClass> to List<BaseClass> 【发布时间】:2011-07-02 05:00:32 【问题描述】:

我有一个List&lt;SubClass&gt;,我想将其视为List&lt;BaseClass&gt;。看起来这应该不是问题,因为将 SubClass 转换为 BaseClass 很简单,但我的编译器抱怨说不可能进行转换。

那么,获取与 List&lt;BaseClass&gt; 相同的对象的引用的最佳方法是什么?

现在我只是制作一个新列表并复制旧列表:

List<BaseClass> convertedList = new ArrayList<BaseClass>(listOfSubClass)

但据我了解,必须创建一个全新的列表。如果可能的话,我想参考原始列表!

【问题讨论】:

你的答案在这里:***.com/questions/662508/… 【参考方案1】:

这种赋值的语法使用通配符:

List<SubClass> subs = ...;
List<? extends BaseClass> bases = subs;

重要的是要意识到List&lt;SubClass&gt;List&lt;BaseClass&gt;不可互换的。保留对List&lt;SubClass&gt; 的引用的代码将期望列表中的每个项目都是SubClass。如果代码的另一部分将列表称为List&lt;BaseClass&gt;,则在插入BaseClassAnotherSubClass 时编译器不会报错。但这会导致第一段代码出现ClassCastException,它假定列表中的所有内容都是SubClass

泛型集合的行为与 Java 中的数组不同。数组是协变的;也就是说,允许这样做:

SubClass[] subs = ...;
BaseClass[] bases = subs;

这是允许的,因为数组“知道”其元素的类型。如果有人试图在数组中存储不是SubClass 实例的东西(通过bases 引用),则会引发运行时异常。

泛型集合“知道”它们的组件类型;此信息在编译时被“擦除”。因此,当发生无效存储时,它们不能引发运行时异常。相反,当从集合中读取值时,ClassCastException 将在代码中某个遥远的、难以关联的点引发。如果您注意编译器关于类型安全的警告,您将在运行时避免这些类型错误。

【讨论】:

应注意,使用数组,而不是 ClassCastException 检索非 SubClass(或派生)类型的对象时,插入时会得到 ArrayStoreException。 特别感谢您解释为什么这两个列表不能被视为相同。 Java 类型系统假装数组是协变的,但它们并不是真正可替代的,ArrayStoreException 证明了这一点。【参考方案2】:

erickson 已经解释了为什么你不能这样做,但这里有一些解决方案:

如果您只想从基本列表中取出元素,原则上您的接收方法应声明为采用List&lt;? extends BaseClass&gt;

但如果不是并且您无法更改它,您可以使用Collections.unmodifiableList(...) 包装列表,这允许返回参数参数超类型的列表。 (它通过在插入尝试时抛出 UnsupportedOperationException 来避免类型安全问题。)

【讨论】:

太棒了!不知道那个。 PS:add(null) 是合法的【参考方案3】:

正如@erickson 解释的那样,如果您真的想要引用原始列表,如果您想在原始声明下再次使用它,请确保没有代码向该列表插入任何内容。获取它的最简单方法是将其转换为一个普通的旧非通用列表:

List<BaseClass> baseList = (List)new ArrayList<SubClass>();

如果您不知道 List 会发生什么,我不建议您这样做,并且建议您更改任何需要 List 的代码以接受您拥有的 List。

【讨论】:

【参考方案4】:

我错过了您刚刚使用双重演员转换原始列表的答案。所以这里是为了完整性:

List<BaseClass> baseList = (List<BaseClass>)(List<?>)subList;

没有任何复制,操作速度很快。但是,您在这里欺骗了编译器,因此您必须绝对确保不要以 subList 开始包含不同子类型的项目的方式修改列表。在处理不可变列表时,这通常不是问题。

【讨论】:

【参考方案5】:

下面是一个有用的sn-p。它构造了一个新的数组列表,但 JVM 对象创建开销并不重要。

我看到其他答案不必要地复杂。

List<BaseClass> baselist = new ArrayList<>(sublist);

【讨论】:

感谢您提供此代码 sn-p,它可能会提供一些即时帮助。一个正确的解释would greatly improve 它的教育价值通过展示为什么这是一个很好的解决问题的方法,并将使它对未来有类似但不相同的问题的读者更有用。请edit您的答案添加解释,并说明适用的限制和假设。特别是,这与创建新列表的问题中的代码有何不同? 开销并非微不足道,因为它还必须(浅)复制每个引用。因此它会随着列表的大小而缩放,所以它是一个 O(n) 操作。【参考方案6】:

List&lt;BaseClass&gt; convertedList = Collections.checkedList(listOfSubClass, BaseClass.class)

【讨论】:

这应该行不通,因为这个方法的所有参数和结果都采用相同的类型参数。 奇怪,你是对的。出于某种原因,我的印象是这种方法对于向上转换通用集合很有用。仍然可以以这种方式使用,如果您想使用抑制警告,它将提供“安全”集合。【参考方案7】:

您正在尝试做的事情非常有用,我发现我需要在我编写的代码中经常这样做。一个示例用例:

假设我们有一个接口Foo,我们有一个zorking 包,它有一个ZorkingFooManager,它创建和管理包私有ZorkingFoo implements Foo 的实例。 (一个非常常见的场景。)

所以,ZorkingFooManager 需要包含一个private Collection&lt;ZorkingFoo&gt; zorkingFoos,但它需要公开一个public Collection&lt;Foo&gt; getAllFoos()

大多数 Java 程序员在实现 getAllFoos() 之前不会三思而后行,因为它分配了一个新的 ArrayList&lt;Foo&gt;,用来自 zorkingFoos 的所有元素填充它并返回它。我很享受这样的想法,即在全球数百万台机器上运行的 java 代码所消耗的所有时钟周期中,大约 30% 的时钟周期除了创建这些无用的 ArrayList 副本之外什么也没做,这些副本在创建后的几微秒内就会被垃圾收集。

这个问题的解决方案当然是向下转换集合。这是最好的方法:

static <T,U extends T> List<T> downCastList( List<U> list )

    return castList( list );

这将我们带到castList()函数:

static <T,E> List<T> castList( List<E> list )

    @SuppressWarnings( "unchecked" )
    List<T> result = (List<T>)list;
    return result;

中间的result变量是必需的,因为java语言的变态:

return (List&lt;T&gt;)list; 产生“未经检查的强制转换”异常;到目前为止,一切都很好;但是:

@SuppressWarnings( "unchecked" ) return (List&lt;T&gt;)list; 是对 suppress-warnings 注释的非法使用。

因此,即使在 return 语句上使用 @SuppressWarnings 不符合规定,但在赋值上使用它显然是可以的,因此额外的“结果”变量解决了这个问题。 (无论如何,它都应该被编译器或 JIT 优化掉。)

【讨论】:

【参考方案8】:

如何投射所有元素。它将创建一个新列表,但会引用旧列表中的原始对象。

List<BaseClass> convertedList = listOfSubClass.stream().map(x -> (BaseClass)x).collect(Collectors.toList());

【讨论】:

这是将子类列表转换为超类的完整代码。 在调用.map() 之前不需要调用列表中的.stream() 吗? 是的。已修复(添加了 stream())。【参考方案9】:

这是使用泛型将子类列表转换为超类的完整工作代码。

传递子类类型的调用方法

List<SubClass> subClassParam = new ArrayList<>();    
getElementDefinitionStatuses(subClassParam);

接受基类的任何子类型的被调用者方法

private static List<String> getElementDefinitionStatuses(List<? extends 
    BaseClass> baseClassVariableName) 
     return allElementStatuses;
    

【讨论】:

【参考方案10】:

这样的事情也应该起作用:

public static <T> List<T> convertListWithExtendableClasses(
    final List< ? extends T> originalList,
    final Class<T> clazz )

    final List<T> newList = new ArrayList<>();
    for ( final T item : originalList )
    
        newList.add( item );
    // for
    return newList;

不知道为什么 Eclipse 需要 clazz..

【讨论】:

以上是关于将 List<SubClass> 转换为 List<BaseClass> 的最有效方法的主要内容,如果未能解决你的问题,请参考以下文章

将 List<List<Integer>> 转换为 int[][]

如何将 List<string> 转换为 List<myEnumType>?

如何将 List<Object> 转换为 List<MyClass>

将 List<List<string>> 转换为 string[][] 的快速方法是啥?

将 List<DerivedClass> 转换为 List<BaseClass>

将 List<Class> 转换为 List<Class.property>