【问题标题】:Clean solution to "cast" ObservableList<Foo> to List<IFoo>?将 ObservableList<Foo> 转换为 List<IFoo> 的清洁解决方案?
【发布时间】:2015-12-02 15:01:38
【问题描述】:

我的应用程序有一个简单的插件 API。它打包在插件开发人员可以使用的单独 JAR 中。核心应用实现 API 中包含的接口,并将它们暴露给加载了ServiceLoader 的插件。

考虑这段代码:

public interface ILayer {}

public class Layer implements ILayer {
    public void internalMethod() { /*snip*/ }
}

public interface IPlotter {
    List<ILayer> getLayers();
}

public class Plotter implements IPlotter {
    private ObservableList<Layer> layers = FXCollections.observableArrayList();

    @Override
    public ObservableList<Layer> getLayers() {  // incompatible returned type
        return layers;
    }
}

我的要求是:

  • 在内部,ObservableListLayers 可用
  • API 仅公开 ListILayers

很遗憾,将ObservableList&lt;Layer&gt; 转换为List&lt;ILayer&gt; 是非法的。

在 API 中返回List&lt;Layer&gt; 会暴露Layer 中的internalMethod(),所以不好。

我也可以有private ObservableList&lt;ILayer&gt; layers,然后实际上将Layers 存储在里面,但是每次我使用internalMethod() 时都需要转换它的项目,而且我对这个想法不是很感兴趣。

这个问题有干净的解决方案吗?

【问题讨论】:

  • 您可以使用return (List&lt;ILayer&gt;)(List&lt;?&gt;)layers; 克服转换错误

标签: java generics javafx interface casting


【解决方案1】:

一种选择是更改接口方法的签名以允许返回 ILayer 的子类型:

interface IPlotter {
    List<? extends ILayer> getLayers();
}

class Plotter implements IPlotter {
    private ObservableList<Layer> layers = FXCollections.observableArrayList();

    @Override
    public List<? extends ILayer> getLayers() {
        return layers;
    }
}

主要缺点是调用List&lt;? extends ILayer&gt; layers = plotter.getLayers(); 的人将无法添加到列表中:layers.add(someLayer); 将无法编译。这可能是也可能不是问题。

【讨论】:

  • 这是个好主意,但并不能完全解决我的问题。在其他核心应用程序类中调用 getLayers() 时,我仍然需要强制转换。
  • 你可以添加一个私人包ObservableList&lt;Layer&gt; getLayersNoCast()或类似的东西......
  • @gronostaj:但是你又把Layer 泄露给了其他核心类,不是吗?
  • @user140547,这就是我想要实现的。核心应用可以为所欲为,API 暴露了有限的接口。
  • 那么你可以创建一个核心应用程序使用的内部方法,并创建一个新的 List,它由getLayers() 返回。剩下的问题是如何使这两个列表保持同步(以及 API 是否提供此类方法)。
【解决方案2】:

@assylias 的回答为该问题提供了最干净的解决方案。事实是 List&lt;Layer&gt; 没有问题 不是 List&lt;ILayer&gt; (考虑:您可以将不是 LayerILayer 添加到后者),而 Java 没有接受它认为永远不会正确的强制转换。

但是,如果您确实想准确返回 List&lt;ILayer&gt;,那么您至少有三个不错的选择:

  1. 首先创建一个List&lt;ILayer&gt;,并在Plotter.getLayers() 中进行必要的转换。只要您(非常合理地)不喜欢投射,您也可以
  2. 在内部使用List&lt;Layer&gt;,然后创建一个List&lt;Ilayer&gt;从方法中返回,例如

    return new ArrayList<ILayer>(layers);
    
  3. 作为 (2) 的变体,您可以创建包装列表而不是副本,假设返回的列表不可修改是可以的:

    return Collections.<ILayer>unmodifiableList(layers);
    

    注意使用显式类型参数为您提供所需的结果类型参数化。不过也请注意,与 assylias 的 List&lt;? extends ILayer&gt; 方法相比,这种替代方法对返回值的使用施加了更多限制。

【讨论】:

  • 哦,现在似乎很明显为什么这种强制转换是非法的。感谢您的澄清!
【解决方案3】:

另一种解决方案可能是使用bindContent 实用方法:

public class Plotter implements IPlotter {
    private final ObservableList<Layer> layers = FXCollections.observableArrayList();
    private final List<ILayer> ilayers = new ArrayList<>;

    public Plotter(){
        Bindings.bindContent(ilayers , layers);
    }

    @Override
    public List<ILayer> getLayers() { 
        return ilayers;
    }
}

它还有一个缺点,就是你不应该操纵 getLayers 返回的 List。来自 Javadoc:

一旦一个 List 绑定到一个 ObservableList,这个 List 就不能被 直接改了。这样做会导致意想不到的结果。

【讨论】:

    【解决方案4】:

    如果适用,您可以尝试将internalMethod 移动到ILayer。或尽可能将其删除。如果这些都不可能,您可以尝试将IPlotter 设为通用。然后,IPlotter&lt;Layer&gt; 是另一种类型 IPlotter&lt;Layer2&gt;

    interface ILayer {
    }
    
    class Layer implements ILayer {
        public void internalMethod() { /*snip*/ }
    }
    
    class Layer2 implements ILayer {
        public void internalMethod() { /*snip*/ }
    }
    
    interface IPlotter<T extends ILayer> {
        List<T> getLayers();
    }
    
    class Plotter implements IPlotter<Layer> {
        private ObservableList<Layer> layers = FXCollections.observableArrayList();
    
        @Override
        public ObservableList<Layer> getLayers() {  // incompatible returned type
            return layers;
        }
    } 
    class Plotter2 implements IPlotter<Layer2> {
        private ObservableList<Layer2> layers = FXCollections.observableArrayList();
    
        @Override
        public ObservableList<Layer2> getLayers() {  // incompatible returned type
            return layers;
        }
    }
    

    如果您有多个绘图仪用于不同的图层,则必须使用

    List<IPlotter<? extends ILayer>> i  = Arrays.asList(new Plotter(), new Plotter2());
    

    【讨论】:

    • internalMethod() 的全部意义在于拥有一个无法通过 API 获得的 internal 方法。我需要一个ObservableList 内部的Layers 和ListILayers 外部,这两者必须始终保持同步。
    • 好吧,我想那么您可以按照其他人的建议使用List&lt;? extends ILayer&gt;,或者如果ObservableList 提供了使它们保持同步的功能,则可以维护两个列表,因为列表不能同时具有@ 987654335@和Layer一次。
    【解决方案5】:

    怎么样 private ObservableList&lt;? extends ILayer&gt; layers = FXCollections.observableArrayList();?

    【讨论】:

    • OP 仍然需要强制转换才能调用 internalMethod。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-07
    • 1970-01-01
    相关资源
    最近更新 更多