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

我有一个 List<SubClass>,我想治疗作为一个 List<BaseClass>。这似乎不应该是一个问题,因为强制转换一个 SubClass到一个 BaseClass是一个快照,但我的编译器抱怨强制转换是不可能的。

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

现在我只是在做一个新的清单,然后复制旧的清单:

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

但据我所知,这需要创建一个全新的名单。如果可能的话,我想要一份原始列表的参考资料!

87306 次浏览

此类赋值的语法使用通配符:

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

重要的是要认识到,List<SubClass>没有List<BaseClass>可互换的。保留对 List<SubClass>引用的代码将期望列表中的每个项目都是 SubClass。如果代码的另一部分将列表称为 List<BaseClass>,则当插入 BaseClassAnotherSubClass时,编译器将不会抱怨。但是这将导致第一段代码出现 ClassCastException,它假设列表中的所有内容都是 SubClass

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

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

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

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

正如@erickson 解释的那样,如果你真的想要一个对原始列表的引用,那么如果你想要在它的原始声明下再次使用它的话,请确保没有代码插入任何东西到那个列表中。获取它的最简单方法是将它强制转换为一个普通的非泛型列表:

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

我不会建议这样做,如果你不知道什么发生在名单,并会建议你改变任何代码需要名单,以接受你有名单。

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

如果您只想从基列表中提取元素,那么原则上您的接收方法应该声明为提取 List<? extends BaseClass>

但是如果它不是并且您不能更改它,您可以用 Collections.unmodifiableList(...)包装该列表,这允许返回参数参数的超类型 List。(它通过在插入尝试时引发 Unsupport tedOperationException 来避免类型安全问题。)

这样的做法也应该奏效:

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. 。

您正在尝试做的事情非常有用,而且我发现我需要经常用我编写的代码来做这件事情。

大多数 Java 程序员在实现 getConvertedList()之前不会三思而后行,他们会分配一个新的 ArrayList<>(),用原始列表中的所有元素填充它,然后返回它。我很享受这样一种想法: 在遍布全球的数百万台计算机上运行的 Java 代码所消耗的大约30% 的时钟周期中,除了创建无用的 ArrayList副本之外什么也不做,而这些副本在创建之后几微秒就被垃圾收集了。

当然,这个问题的解决方案是向下转换集合:

static <T,U extends T> List<T> downCastList( List<U> list )
{
@SuppressWarnings( "unchecked" )
List<T> result = (List<T>)list;
return result;
}

中间的 result变量是必要的,因为 Java 语言的一个反常现象:

  • return (List<T>)list;将产生一个“未检查的铸造”警告;

  • 为了抑制警告,您需要一个 @SuppressWarnings( "unchecked" )注释,而良好的编程实践要求它必须放置在尽可能小的范围内,即单独的语句,而不是方法。

  • 在 java 中,注释不能放在任何代码行上; 它必须放在某个实体上,比如类、字段、方法等。

  • 幸运的是,其中一个可注释的实体是局部变量声明。

  • 因此,我们必须声明一个新的局部变量来对其使用 @SuppressWarnings注释,然后返回该变量的值。(无论如何它都不应该重要,因为它应该被 JIT 优化掉。)

注意: 这个答案刚刚被投了反对票,这很酷,但是如果你正在阅读这篇文章,请一定要阅读我对这个相同问题的第二个,更近期的回答: https://stackoverflow.com/a/72195980/773113

下面是一个有用的代码片段。它构造了一个新的数组列表,但是 JVM 对象的创建并不重要。

我看到其他答案并不复杂。

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

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

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

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

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

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

方法,该方法接受基类的任何子类型

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

我错过了您刚才使用双重强制转换对原始列表进行强制转换的答案。因此,这里是完整性:

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

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

实现这一目标的最有效的同时也是 安全的方法如下:

List<S> supers = List.copyOf( descendants );

这个函数的文档在这里: Oracle.com-Java SE 19 docs-List.copOf ()这个文档说明这个函数存在“从: 10开始”。

使用这一功能具有以下优点:

  • 这是一个简洁的笑话。
  • 它不会产生任何警告。
  • 它不需要任何类型转换。
  • 它不需要繁琐的 List<? extends S>构造。
  • 它确实 不一定做了一个副本! ! !
  • 最重要的是: 这样做是对的。(它是安全的。)

为什么这么做是对的?

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

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

如果你的原始 List<D>是一个 ArrayList<D>,那么为了获得一个 List<S>,一个 ArrayList ArrayList<D>2的副本。如果一个模型被制作出来,那么它就会打开一个可能性,无意中在 List<S>中加入一个 S,导致你原来的 ArrayList<D>D中包含一个 S,这是一个被称为 ArrayList<D>3(ArrayList<D>4)的灾难性情况: 尝试在原来的 ArrayList<D>中迭代所有的 D将抛出一个 ArrayList<D>1。

另一方面,如果您的原始 List<D>是使用 List.of()创建的,那么它是不可更改的 (* 1),所以可以简单地将它转换为 List<S>,因为没有人可以在 D中实际添加 S

List.copyOf()为您处理这个决策逻辑。


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