如何将List<? extends BaseClass>赋值给List<BaseClass>?

6

having

class BaseClass implements IData ();
class ChildClassA() extends BaseClass;
class ChildClassB() extends BaseClass;

无法执行

List<BaseClass> aList = new ArrayList<ChildClassA>()

所以有一个:
List<? extends IData> aList 
for pointint to either 
    ArrayList<ChildClassA>(),  
or 
    ArrayList<ChildClassB>()
aList是在运行时通过其他路由构建的,那部分代码有一个函数来获取aList中的List<IData>
问题是如果List<? extends IData> aList指向ArrayList<ChildClassA>()ArrayList<ChildClassB>(),它是否可以执行类似以下的操作:ListData<IData> outputList = (List<IData>) aList
(看起来这个方法可行,但不确定是否有更好的方法来分配泛型数组而不是强制转换。) 编辑:List<IData> outputList的输出仅供只读使用(不可变),不能对其进行插入/删除操作,它只能迭代IData以针对IData的实际内容作出反应。
List<? extends IData> aList =  new ArrayList<ChildClassA>();
ListData<IData> outputList = (List<IData>)aList

List<? extends IData> aList =  new ArrayList<ChildClassB>();
ListData<IData> outputList = (List<IData>)aList
2个回答

3
不行,这是不安全的。
在进行类型转换后,往仅应包含ChildClassA类型元素的列表中添加其他子类类型ChildClassB的元素或反之亦然将被认为是合法的。
我们可以对您的代码进行简化,以更清楚地说明为什么不应该允许这样做:
List<ChildClassA> aList =  new ArrayList<ChildClassA>();
aList.add(a1);
aList.add(a2);
//...
List<IData> iDataList = (List<IData>) aList;
iDataList.add(b1);
iDataList.add(b2);
//...
for (ChildClassA a : aList) {
   // a some point a is going to be assigned b1 or b2 and they results in cast
   // exception.
}

请注意,iDataList 引用与 aList 相同的列表对象。 如果允许进行类型转换,那么您将能够向 aList 添加不是 ChildClassA 实例的元素。
最佳解决方案详见细节。
如果问题在于第三方库需要引用类型为 List<IData> 的参数,并且只要是仅供读取,您可以使用由 Collections.unmodifiableList 返回的不可修改代理。
import java.util.Collections;
//...
final List<ChildClassA> aList = new ArrayList<>();
//... add stuff to aList
final List<IData> readOnlyIDataList = Collections.unmodifiableList(aList); 
//... read only access operations readOnlyIDataList 

@Izruo 的意思是要写ChildClassA,谢谢你的指出。 - Valentin Ruano
谢谢!我更新了问题,这里的List<IData>是只读的,不能添加/删除。如果它是只读的,还有其他问题吗? - lannyf
@lannyf 如果是这样的话,解决方案就很简单了,不要再进行强制转换了,List<? extends IData> 就足够好了。是什么阻止你使用通配符类型的列表变量呢? - Valentin Ruano
你是对的。问题就在于 List<? extends IData> aList 这一部分处于中心位置,它可能会在运行时被赋值为 List<ChildClassA> 或者 List<ChildClassB>。而且它将用于调用其他库代码,这些代码只接受 List<IData>。因此需要找到一种方法将 List<? extends T> 分配给 List<T>。再次强调,List<T> 将是只读的。 - lannyf
@lannyf 看起来库只接受 List<IData> 的限制应该是你问题的一部分。我认为最直接的解决方案就是复制列表,除非有性能问题。除非是真正的问题(避免过早优化),否则不必担心内存或 CPU。如果您坚持避免复制,并且由于库不打算更改列表,您可以使用 Collections.unmodifiableList(aList) 为实际列表提供一个安全代理... 实际上后者是最好的选择,因为它将返回一个 List<IData>!!! - Valentin Ruano
显示剩余2条评论

3

简而言之 使用Collections#unmodifiableList

List<IData> outputList = Collections.unmodifiableList(aList);

如果您想了解更多关于这个主题的信息,您可能需要熟悉 PECS 原则。


这是不可能的,因为这两种类型不兼容。

List<BaseClass> 是一个声明,它只包含 BaseClass 对象的列表。更准确地说,它提供以下两个保证:

  • 从中检索的对象可以分配给 BaseClass
  • 可以将可以分配给 BaseClass 的每个对象添加到其中(但不能添加其他对象)

List<? extends BaseClass> 是一个更宽松的声明。确切地说,它不提供第二个保证。然而,不仅保证消失了,而且现在无法向其中添加项目,因为列表的精确泛型类型未定义。甚至对于相同的列表声明(而不是相同的列表对象),它也可能在运行时发生更改。

因此,List<? extends BaseClass> 不能分配给 List<BaseClass>,因为后者提供了前者无法实现的保证。


实际上,考虑以下方法:

public List<BaseClass> makeList() {
    // TODO implement method
    return null;
}

如果有人实现这个方法,返回一个List<? extends BaseClass>,使用此方法的客户端将无法添加项目,尽管其声明表明可以添加。因此,这样的赋值会导致编译错误。为了解决这个例子中的问题,可以在方法中添加宽松的声明。
public List<? extends BaseClass> makeList() {
    // TODO implement method
    return null;
}

这将向每个客户端发出信号,表明从此方法返回的列表不适用于添加项目。


现在让我们回到您的使用情况。我认为最合适的修复方法是重新构造该函数

从 aList 中获取一个列表。

看起来当前声明为:

public void useList(List<BaseClass> list);

但是由于它不会向列表中添加任何项,因此应该声明为:

但是由于它不会向列表中添加任何项,因此应该声明为

public void useList(List<? extends BaseClass> list);

然而,如果该方法是当前不可更改的API的一部分,您仍然可以执行以下操作:
List<? extends BaseClass> list;
....
List<BaseClass> tmp = Collections.unmodifiableList(list);
useList(tmp);

@lzruo,感谢解释。这里的用例有点不同(我会更新问题),输出的List<IData>是不可变的,并且使用它的用户只会迭代并获取IData,然后找出真正的数据。因此,在这里,List<? extends IData>只是对List<ChildClassA>(或List<ChildClassB>)实例的引用,因此将List<? extends IData>转换为List<IData>并不会改变任何内容,更重要的是此转换的输出List<IData>是只读的。 - lannyf
@lannyf 我花了一些时间,但现在我有一个相当方便的解决方案。 - Izruo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接