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

Ril*_*ark 127 java inheritance casting

我有一个List<SubClass>我想要作为一个List<BaseClass>.看起来它应该不是一个问题,因为将a转换SubClass为a BaseClass是一个快照,但我的编译器抱怨演员表是不可能的.

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

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

List<BaseClass> convertedList = new ArrayList<BaseClass>(listOfSubClass)
Run Code Online (Sandbox Code Playgroud)

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

eri*_*son 162

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

List<SubClass> subs = ...;
List<? extends BaseClass> bases = subs;
Run Code Online (Sandbox Code Playgroud)

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

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

SubClass[] subs = ...;
BaseClass[] bases = subs;
Run Code Online (Sandbox Code Playgroud)

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

通用集合 "知道"它们的组件类型; 这些信息在编译时被"擦除".因此,当发生无效存储时,它们无法引发运行时异常.相反,ClassCastException当从集合中读取值时,将在代码中的某个远距离,难以关联的点处引发a.如果您注意有关类型安全性的编译器警告,您将在运行时避免这些类型错误.


Paŭ*_*ann 38

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

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

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


Mik*_*kis 16

实现此目的最有效且同时安全的方法如下:

List<S> supers = List.copyOf( descendants );
Run Code Online (Sandbox Code Playgroud)

该函数的文档位于:oracle.com - Java SE 19 docs - List.copyOf() 该文档指出该函数存在“自:10”。

使用该功能有以下优点:

  • 这是一篇简洁的单行诗。
  • 它不会产生任何警告。
  • 它不需要任何类型转换。
  • 它不需要繁琐的List<? extends S>构造。
  • 它不一定复制!
  • 最重要的是:它做了正确的事。(这是安全的。)

为什么这是正确的事情?

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

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

如果您的原件List<D>是一份ArrayList<D>,那么为了获得一份,必须制作List<S>一份副本。如果进行了强制转换,则可能会无意中添加 an到 that 中,导致您的原始数据在 s中包含 an ,这是一种灾难性的情况,称为堆污染维基百科):尝试迭代所有s在原来的情况下会抛出一个.ArrayList SList<S>ArrayList<D>SDDArrayList<D>ClassCastException

另一方面,如果您的原始文件List<D>是使用 创建的List.of(),那么它是不可更改的(*1),因此可以简单地将其转换为,因为没有人可以真正在s 中List<S>添加 an 。SD

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


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


hd4*_*d42 11

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

List<BaseClass> baseList = (List)new ArrayList<SubClass>();
Run Code Online (Sandbox Code Playgroud)

如果您不知道列表会发生什么,我建议您更改列表中接受列表的任何代码.


joh*_*384 6

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

List<BaseClass> baseList = (List<BaseClass>)(List<?>)subList;
Run Code Online (Sandbox Code Playgroud)

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