将List <SubClass>转换为List <BaseClass>最有效的方法
我有一个List<SubClass>
,我想作为一个List<BaseClass>
。 看起来这不应该是一个问题,因为投下一个SubClass
到一个BaseClass
是一个快照,但我的编译器抱怨说,转换是不可能的。
那么,什么是最好的方式来获得一个List<BaseClass>
相同的对象的引用?
现在我只是创build一个新的列表,并复制旧的列表:
List<BaseClass> convertedList = new ArrayList<BaseClass>(listOfSubClass)
但据我所知,这必须创build一个全新的名单。 如果可能,我想参考原始列表!
这种分配的语法使用通配符:
List<SubClass> subs = ...; List<? extends BaseClass> bases = subs;
意识到List<SubClass>
不能与List<BaseClass>
互换是很重要的。 保留对List<SubClass>
的引用的代码将期望列表中的每个项目都是一个SubClass
。 如果代码的另一部分作为List<BaseClass>
引用列表,编译器将不会在插入BaseClass
或AnotherSubClass
时发生抱怨。 但是,这将导致第一段代码的ClassCastException
,它假定列表中的所有内容都是一个SubClass
。
generics集合与Java中的数组行为不同。 数组是协变的; 也就是说,允许这样做:
SubClass[] subs = ...; BaseClass[] bases = subs;
这是允许的,因为数组“知道”其元素的types。 如果有人试图在数组中存储不是SubClass
实例的东西(通过bases
引用),则会抛出一个运行时exception。
通用集合不 “知道”他们的组件types; 这个信息在编译时被“擦除”。 因此,当发生无效存储时,它们不能引发运行时exception。 相反,当从集合中读取一个值时,会在代码中的一些远距离,难以联系的地方引发ClassCastException
。 如果你注意编译有关types安全的警告,你将会在运行时避免这些types的错误。
埃里克森已经解释了为什么你不能这样做,但在这里有一些解决scheme:
如果你只想从你的基础列表中取出元素,原则上你的接收方法应该被声明为一个List<? extends BaseClass>
List<? extends BaseClass>
。
但是,如果它不是,你不能改变它,你可以包裹列表Collections.unmodifiableList(...)
,它允许返回参数的超参数的参数的列表。 (它通过在插入尝试时抛出UnsupportedOperationException来避免types安全问题。)
正如@erickson所解释的,如果你真的想要引用原始列表,那么确保没有任何代码将任何东西插入到列表中,如果你想在原始的声明下再次使用它的话。 得到它的最简单的方法就是把它转换成一个普通的旧的非generics列表:
List<BaseClass> baseList = (List)new ArrayList<SubClass>();
我不会推荐这个,如果你不知道列表会发生什么,并build议你改变任何代码需要列表来接受你的列表。
List<BaseClass> convertedList = Collections.checkedList(listOfSubClass, BaseClass.class)
你正在做的事情是非常有用的,我发现我需要经常在我编写的代码中这样做。 一个示例用例:
假设我们有一个Foo
接口,并且有一个zorking
包,它有一个ZorkingFooManager
,它创build并pipe理包 – 私有ZorkingFoo implements Foo
实例。 (一个非常常见的情况)
所以, ZorkingFooManager
需要包含一个private Collection<ZorkingFoo> zorkingFoos
但是它需要公开一个public Collection<Foo> getAllFoos()
。
大多数java程序员在实现getAllFoos()
之前不会考虑三次分配一个新的ArrayList<Foo>
,并用zorkingFoos
所有元素zorkingFoos
并返回它。 我喜欢这样的想法:在全球数百万计算机上运行的java代码所消耗的所有时钟周期中,大约30%的时间周期没有任何作用,只是创build了无用的ArrayLists副本,这些副本在创build之后被微秒收集。
当然,解决这个问题的办法是向下收集这个集合。 这是做到这一点的最好方法:
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; }
由于Java语言的颠倒,中间result
variables是必需的:
-
return (List<T>)list;
产生“未经检查的强制转换”exception; 到现在为止还挺好; 但是之后: -
@SuppressWarnings( "unchecked" ) return (List<T>)list;
是压制警告注释的非法使用。
因此,尽pipe在return
语句中使用@SuppressWarnings
并不是@SuppressWarnings
,但在赋值上使用它显然是好的,所以额外的“result”variables解决了这个问题。 (无论如何,它应该被编译器或者JIT优化掉。)
像这样的东西也应该工作:
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
下面是一个有用的代码片段。 它构造了一个新的数组列表,但是JVM对象的头创build是不重要的。
我看到其他答案不一定复杂。
List<BaseClass> baselist = new ArrayList<>(sublist);