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


134

我有一个List<SubClass>我想当作一个List<BaseClass>。因为将a强制转换SubClass为a 似乎很BaseClass容易,但这似乎不成问题,但是我的编译器抱怨说强制转换是不可能的。

那么,获得与相同对象的引用的最佳方法是List<BaseClass>什么?

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

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

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


Answers:


175

这种分配的语法使用通配符:

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

要认识到,这是很重要的List<SubClass>具有互换性List<BaseClass>。保留对List<SubClass>will 的引用的代码将期望列表中的每个项目都是SubClass。如果另一部分代码将该列表称为a List<BaseClass>,则在插入BaseClassor 时编译器将不会发出任何投诉AnotherSubClass。但这将ClassCastException在第一段代码中产生一个,假设列表中的所有内容都是一个SubClass

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

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

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

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


应该注意的是,使用Arrays检索非SubClass类型(或派生的)类型的对象时,可以使用ArrayStoreException而不是ClassCastException。
Axel

特别感谢您对为什么不能将两个列表视为相同的解释。
Riley Lark

Java类型系统假装数组是协变的,但实际上并不能被ArrayStoreException证明是可替换的。
圣保罗Ebermann

38

erickson已经解释了为什么无法执行此操作,但是这里提供了一些解决方案:

如果只想从基本列表中删除元素,则原则上应将您的接收方法声明为采用a List<? extends BaseClass>

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


大!不知道那个。
keuleJ 2014年

非常感谢你!
Woland

14

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

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

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


3

下面是一个有用的代码片段。它构造了一个新的数组列表,但是创建JVM对象的开销微不足道。

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

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

1
感谢您提供此代码段,它可能会立即提供帮助。通过说明为什么这是一个解决问题的好方法,适当的解释将大大提高其教育价值,并且对将来有相似但不相同问题的读者更有用。请编辑您的答案以添加说明,并指出适用的限制和假设。特别是,这与创建新列表的问题中的代码有何不同?
Toby Speight

开销并不是微不足道的,因为它也必须(浅)复制每个引用。这样,它随列表的大小缩放,因此它是O(n)操作。
john16384

2

我错过了答案,在这里您只是使用两次强制转换将原始列表转换了。所以这里是为了完整性:

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

什么也不会复制,并且操作很快。但是,您在这里欺骗了编译器,因此必须绝对确保不要以使subList开头包含不同子类型的项的方式修改列表。当处理不可变列表时,这通常不是问题。


1

List<BaseClass> convertedList = Collections.checkedList(listOfSubClass, BaseClass.class)


5
这不应该起作用,因为对于这种方法,所有参数和结果都采用相同的类型参数。
圣保罗Ebermann

奇怪,你是对的。出于某种原因,我给人的印象是该方法对于转换通用集合很有用。仍可以这种方式使用,如果您要使用抑制警告,它将提供一个“安全”的集合。
jtahlborn 2011年

1

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

假设我们有一个接口Foo,并且有一个zorking包,其中有一个ZorkingFooManager创建和管理package-private实例的包ZorkingFoo implements Foo。(一种非常常见的情况。)

因此,ZorkingFooManager需要包含一个,private Collection<ZorkingFoo> zorkingFoos但需要暴露一个public Collection<Foo> getAllFoos()

大多数Java程序员在实现getAllFoos()一个新对象之前不会三思而后行,因为它分配了一个new ArrayList<Foo>,并使用其中的所有元素填充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<T>)list;产生“未经检查的演员表”异常;到目前为止,一切都很好; 但是之后:

  • @SuppressWarnings( "unchecked" ) return (List<T>)list; 是禁止使用警告注释的非法使用。

因此,即使不是@SuppressWarningsreturn语句中使用它,也可以在赋值中使用它,这显然很好,因此额外的“结果”变量可以解决此问题。(无论如何,应该通过编译器或JIT对其进行优化。)


0

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

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。


0

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

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

这是用于将子类列表转换为超类的完整代码。
kanaparthikiran '18

0

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

传递子类类型的调用者方法

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

接受基类的任何子类型的Callee方法

private static List<String> getElementDefinitionStatuses(List<? extends 
    BaseClass> baseClassVariableName) {
     return allElementStatuses;
    }
}
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.