java中的不可变类型(尤其是集合)和协方差

时间:2016-05-25 08:27:17

标签: java collections immutability

假设我有一个包含WidgetHolder个实例的类Widget。在内部,我用List<Widget>备份它。我保证它是不可变的,所以在构造之后没有小部件被添加到持有者。

WidgetHolder还公开了一个getWidgets()操作,该操作返回小部件列表的不可变视图。

我希望有一个包含SpecificWidgetHolder个实例的派生SpecificWidget类,其getWidgets()的返回类型为Collection<SpecificWidget>

我有相同的保证,即SpecificWidgetHolder构造函数采用SpecificWidget个实例的集合,不会以任何方式对其进行修改。

鉴于WidgetHolderSpecificWidgetHolder都是不可变的,通常的反对证明通用不变性似乎不适用(?)

在Java中有没有一种清晰的表达方式?

谢谢!

2 个答案:

答案 0 :(得分:1)

试试这个:

public class WidgetHolder<T extends Widget>
{
  public List<T> getWidgets()
  {
    return (widgets);
  }

  private List<T> widgets;

} // class WidgetHolder

public class SpecificWidgetHolder extends WidgetHolder<SpecificWidget>
{
}

答案 1 :(得分:1)

您描述的场景非常常见:您在内部存储了一组对象,并希望公开此集合的只读版本。这通常使用Collections#unmodifiableCollection方法(以及其他集合类型的特定方法)来完成:

class WidgetHolder {
    private final List<Widget> widgets = new ArrayList<Widget>();

    public List<Widget> getWidgets() {
        return Collections.unmodifiableList(widgets);
    }
}

但请注意如果该集合无论如何都是不可修改的,您可以通过利用集合不可修改的事实来实现所需的协方差。如果它是不可修改的,您可以将返回类型声明为

public List<? extends Widget> getWidgets() { ... }

这样,每个人都可以像以前一样使用列表。他可以阅读并迭代它。他不能添加新元素,因为它是不可修改的 - 并且一个很好的副作用是这种不可修改性(在某种程度上)在这里可见语法

List<Widget> widgets = widgetHolder.getWidgets();
widgets.add(new Widget()); // throws UnsupportedOperationException AT RUNTIME!

VS

List<? extends Widget> widgets = widgetHolder.getWidgets();
widgets.add(new Widget()); // Does not compile! 

(请注意,widgets.remove(x)widgets.add(null)之类的调用仍会编译,但会抛出UnsupportedOperationException - 它只是一个微妙的提示)

关于您的问题,关键点是您可以使用更具体的返回类型覆盖此方法。 这是真正的协方差

因此,在课程SpecificWidgetHolder中,您可以选择3种覆盖此方法的方法:

  1. 使用与超类中相同的类型:

    public List<? extends Widget> getWidgets()
    
  2. 使用特定类型,您知道它可以分配给超类的类型:

    public List<SpecificWidget> getWidgets()
    
  3. 再次使用通配符类型,为进一步的专业化留出空间(如VerySpecificWidgetHolder):

    public List<? extends SpecificWidget> getWidgets()
    
  4. 如何使用课程有细微差别。特别是,关于&#34;特异性&#34;的信息。可供来电者使用。我试图在这个例子中指出这一点:

    // With option 1, you only receive Widgets - even
    // from a SpecificWidgetHolder
    List<? extends Widget> widgets = specificWidgetHolder.getWidgets();
    Widget widget = widgets.get(0);
    
    // With option 3., you still know that a SpecificWidgetHolder
    // provides a list of SpecificWidgets
    List<? extends SpecificWidget> specificWidgets = specificWidgetHolder.getWidgets();
    SpecificWidget specificWidget = specificWidgets.get(0);
    

    基本上,您必须确定拥有SpecificWidgetHolder的人是否应该能够从中获取SpecificWidget个实例。

    旁注:如另一个答案所示,这也可以用泛型来解决。但我不认为这是必要的。我通常对将类型参数附加到类有点犹豫,因为它们可能比非泛型类型更难理解和维护。泛型(类型参数)就像盐:在正确的位置和正确的剂量,他们可以使事情简单更好。但是当你被它们带走,并最终得到嵌套的泛型和方法签名如<S, T extends Number> Foo<S, ? super T> getFoo()时,这可能会妨碍可读性和可维护性