【问题标题】:Correct way to fix capture conversion in Java在 Java 中修复捕获转换的正确方法
【发布时间】:2020-05-23 12:20:09
【问题描述】:

有一个在集合中使用继承和泛型的分层模型,我有一个与 capture conversion 相关的编译错误:

CaptureConversion.java:16: error: incompatible types: NodeModel cannot be converted to CAP#1
    treeModel.getNodes().set(0, primaryNode);
                                ^
  where CAP#1 is a fresh type-variable:
    CAP#1 extends PublicNodeModel from capture of ? extends PublicNodeModel

我能够克服这个错误的唯一方法是通过以下转换方法:

private static <T extends PublicNodeModel> T convert(PublicNodeModel node) {
    return (T) node;
}

这种转换对我来说看起来很傻,所以我的问题是是否有一些“正确”的方式来处理 Java 中的这种转换?


导致此错误的模型和代码如下(GitHub 上的完整 runnable example)。

简化模型(例如,没有 getter/setter 等):

class PublicTreeModel {
    List<? extends PublicNodeModel> nodes = new ArrayList<>();
}

class TreeModel extends PublicTreeModel {}

class PublicNodeModel {}

class NodeModel extends PublicNodeModel {}

导致编译错误的用法:

TreeModel treeModel = Fixture.createModel();
int index = Fixture.indexOfPrimaryNode(treeModel.getNodes());
NodeModel primaryNode = (NodeModel) treeModel.getNodes().get(index);

// following two lines won't compile
treeModel.getNodes().set(index, treeModel.getNodes().get(0));
treeModel.getNodes().set(0, primaryNode);

使用上述修复(convert() 方法):

// this will compile & run
treeModel.getNodes().set(index, convert(treeModel.getNodes().get(0)));
treeModel.getNodes().set(0, convert(primaryNode));

【问题讨论】:

    标签: java generics type-conversion


    【解决方案1】:
    class PublicTreeModel {
        List<? extends PublicNodeModel> nodes = new ArrayList<>();
    }
    

    不应该能够将任何内容放入该列表中,除了文字 null。这种类型说“它是PublicNodeModelsome 子类,我只是不知道哪个”:因为编译器不知道允许哪个子类,它会阻止您添加任何元素。您应该只使用此列表中的元素,而不是为其提供元素。


    在这种特定情况下,您实际上只是在列表中交换元素,因此有一个简单的解决方案:

    TreeModel treeModel = Fixture.createModel();
    int index = Fixture.indexOfPrimaryNode(treeModel.getNodes());
    Collections.swap(treeModel.getNodes(), 0, index);
    

    也就是说:即使您不知道treeModel.getNodes() 中元素的确切预期子类型,您也知道您尝试放回的元素已从列表中取出;所以它是类型安全的(或者,至少,它的类型安全性不比以前低)。


    在更一般的情况下,还有另外两种解决方案:首先,删除通配符,以便列表接受PublicNodeModel任何子类:

    class PublicTreeModel {
        List<PublicNodeModel> nodes = new ArrayList<>();
    }
    

    如果您确实希望能够依赖 nodes 的元素,特别是 NodeModels,这可能是不可接受的。

    第二种方法是在类中添加一个类型参数,以便在列表中保存元素的类型:

    class PublicTreeModel<T extends PublicNodeModel> {
        List<T> nodes = new ArrayList<>();
    }
    

    在您显示的代码的上下文中存在编译错误,第二种方法可能变为:

    class TreeModel extends PublicTreeModel<NodeModel> {}
    
    TreeModel treeModel = Fixture.createModel();
    int index = Fixture.indexOfPrimaryNode(treeModel.getNodes());
    
    NodeModel primaryNode = treeModel.getNodes().get(index);
    
    treeModel.getNodes().set(index, treeModel.getNodes().get(0));
    treeModel.getNodes().set(0, primaryNode);
    

    【讨论】:

    • 嗯。第二种通用​​解决方案看起来很有希望。我将检查它是否适用于我的上下文(示例高度简化)。
    【解决方案2】:

    如果你想强求,一个想法是做演员:

     ( (List<PublicNodeModel>)treeModel.getNodes()).set(0, primaryNode);
    

    unchecked or unsafe operations 警告为代价,但当然不建议这样做。 @Andy Turner 的答案是要走的路。

    【讨论】:

      猜你喜欢
      • 2017-02-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多