【问题标题】:How to assign List<? extends BaseClass> to List<BaseClass>如何分配列表<?将 BaseClass> 扩展到 List<BaseClass>
【发布时间】:2018-05-23 19:20:35
【问题描述】:

拥有

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&lt;IData&gt; 的功能

问题是List&lt;? extends IData&gt; aList 是指向ArrayList&lt;ChildClassA&gt;() 还是ArrayList&lt;ChildClassB&gt;()

可以ListData&lt;IData&gt; outputList = (List&lt;IData&gt;) aList吗?如下所示:

(似乎它正在工作,但不确定除了强制转换之外是否有更好的方法来分配泛型数组。)

编辑:List&lt;IData&gt; 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

【问题讨论】:

    标签: java generics list-template


    【解决方案1】:

    tl;dr 使用Collections#unmodifiableList

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

    有关此主题的更多信息,您可能需要熟悉PECS 原则。


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

    List&lt;BaseClass&gt; 就是它所声明的,BaseClass 对象的列表。更准确地说,它做了两个保证:

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

    List&lt;? extends BaseClass&gt; 是一个更宽松的声明。准确地说,它根本不做二次保证。然而,不仅保证消失了,而且现在不可能向其中添加项目,因为列表的确切泛型类型是未定义的。它甚至可能在运行时针对同一个列表声明(不是同一个列表对象)发生变化。

    因此,List&lt;? extends BaseClass&gt; 不能分配给 List&lt;BaseClass&gt;,因为后者保证第一个无法实现。


    实际上,考虑以下方法:

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

    如果有人实现了这个方法,返回一个List&lt;? extends BaseClass&gt;,一个使用这个方法的客户端将无法向它添加项目,尽管它的声明另有说明。

    因此,这样的赋值会导致编译错误。

    要解决示例问题,可以在方法中添加松散声明:

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

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


    现在让我们回到您的用例。在我看来,最合适的解决方法是改写该功能

    从 aList 中获取一个 List。

    目前看来它被声明为

    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 并弄清楚它是什么真实数据。所以这里是 List extends IData> 只是对 List (或 List 的实例的引用,因此将 List extends IData> 转换为 List 不会改变任何东西,更重要的是此转换 List 的输出 是只读的。
    • @lannyf 我花了一段时间,但现在我得到了一个非常方便的解决方案。
    【解决方案2】:

    不,不安全。

    在该转换之后,将应该只包含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&lt;IData&gt; 类型的引用,并且只要它仅供阅读,您就可以使用 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 感谢您发现它。
    • 谢谢!我更新了这里的 List 是只读的,没有添加/删除的问题。如果它是只读的,还有其他问题吗?
    • @lannyf 在这种情况下解决方案是微不足道的,不要进一步施放List&lt;? extends IData&gt; 就足够了。是什么阻止您使用通配符类型的列表变量?
    • 你是对的。它只是持有List&lt;? extends IData&gt; aList 的部分位于中心位置,它可能会在运行时分配List&lt;ChildClassA&gt;List&lt;ChildClassB&gt;。它将用于调用其他只需要 List 的库代码。那为什么需要找到一种方法来分配 List 扩展到 List。再次 List 将是只读的。
    • @lannyf 似乎只接受List&lt;IData&gt; 的库限制应该是您问题的一部分。我认为最直接的解决方案就是复制列表,除非存在性能问题。除非是真正的问题(避免过早优化),否则我不会担心内存或 CPU。如果您坚持避免复制,并且由于库不打算更改您编码的列表,请使用Collections.unmodifiableList(aList) 为实际列表提供安全代理...实际上后者是最好的选择,因为事实证明它会返回List&lt;IData&gt;!!!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-21
    • 1970-01-01
    • 2019-12-25
    • 2012-10-17
    • 1970-01-01
    相关资源
    最近更新 更多