将 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<SubClass>
,我想将其视为List<BaseClass>
。看起来这应该不是问题,因为将 SubClass
转换为 BaseClass
很简单,但我的编译器抱怨说不可能进行转换。
那么,获取与 List<BaseClass>
相同的对象的引用的最佳方法是什么?
现在我只是制作一个新列表并复制旧列表:
List<BaseClass> convertedList = new ArrayList<BaseClass>(listOfSubClass)
但据我了解,必须创建一个全新的列表。如果可能的话,我想参考原始列表!
【问题讨论】:
你的答案在这里:***.com/questions/662508/… 【参考方案1】:这种赋值的语法使用通配符:
List<SubClass> subs = ...;
List<? extends BaseClass> bases = subs;
重要的是要意识到List<SubClass>
与List<BaseClass>
是不可互换的。保留对List<SubClass>
的引用的代码将期望列表中的每个项目都是SubClass
。如果代码的另一部分将列表称为List<BaseClass>
,则在插入BaseClass
或AnotherSubClass
时编译器不会报错。但这会导致第一段代码出现ClassCastException
,它假定列表中的所有内容都是SubClass
。
泛型集合的行为与 Java 中的数组不同。数组是协变的;也就是说,允许这样做:
SubClass[] subs = ...;
BaseClass[] bases = subs;
这是允许的,因为数组“知道”其元素的类型。如果有人试图在数组中存储不是SubClass
实例的东西(通过bases
引用),则会引发运行时异常。
泛型集合不“知道”它们的组件类型;此信息在编译时被“擦除”。因此,当发生无效存储时,它们不能引发运行时异常。相反,当从集合中读取值时,ClassCastException
将在代码中某个遥远的、难以关联的点引发。如果您注意编译器关于类型安全的警告,您将在运行时避免这些类型错误。
【讨论】:
应注意,使用数组,而不是 ClassCastException 检索非 SubClass(或派生)类型的对象时,插入时会得到 ArrayStoreException。 特别感谢您解释为什么这两个列表不能被视为相同。 Java 类型系统假装数组是协变的,但它们并不是真正可替代的,ArrayStoreException 证明了这一点。【参考方案2】:erickson 已经解释了为什么你不能这样做,但这里有一些解决方案:
如果您只想从基本列表中取出元素,原则上您的接收方法应声明为采用List<? extends BaseClass>
。
但如果不是并且您无法更改它,您可以使用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<BaseClass> convertedList = Collections.checkedList(listOfSubClass, BaseClass.class)
【讨论】:
这应该行不通,因为这个方法的所有参数和结果都采用相同的类型参数。 奇怪,你是对的。出于某种原因,我的印象是这种方法对于向上转换通用集合很有用。仍然可以以这种方式使用,如果您想使用抑制警告,它将提供“安全”集合。【参考方案7】:您正在尝试做的事情非常有用,我发现我需要在我编写的代码中经常这样做。一个示例用例:
假设我们有一个接口Foo
,我们有一个zorking
包,它有一个ZorkingFooManager
,它创建和管理包私有ZorkingFoo implements Foo
的实例。 (一个非常常见的场景。)
所以,ZorkingFooManager
需要包含一个private Collection<ZorkingFoo> zorkingFoos
,但它需要公开一个public Collection<Foo> getAllFoos()
。
大多数 Java 程序员在实现 getAllFoos()
之前不会三思而后行,因为它分配了一个新的 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;
是对 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[][] 的快速方法是啥?