繁体   English   中英

如何实现可迭代的类并公开真正的不可变的API?

[英]How can I implement an iterable class and expose a truly immutable API?

我想实现一个在Java中可迭代的不可变类。 我通过以下方式进行操作:

public final class MyIterable implements Iterable<Integer> {

    private final Collection<Integer> items;

    public MyIterable(Collection<Integer> items) {
        this.items = Collections.unmodifiableCollection(new LinkedList<Integer>(items));
    }

    @Override
    public Iterator<Integer> iterator() {
        return items.iterator();
    }
}

一切都很好,我无法更改实例。 但是,我的类仍然公开一个API,该API指示可以通过迭代器上的remove()方法修改其内部:

MyIterable myIterable = new MyIterable(Arrays.asList(1, 2, 3));
for (Iterator<Integer> it = myIterable.iterator(); it.hasNext();) {
    it.remove(); // I do not want to expose this even 
                 // though I know it will throw an error at runtime.
    it.next();
}

有什么方法可以避免暴露此remove方法,从而使我的类暴露真正的不变API? 理想的情况是改用ReadOnlyIterable东西,但是Java中似乎没有这种东西。

它似乎借助其方法签名来表明它,但是这些方法专门记录为“不,我们不提供这些行为!”

为了执行此操作,您还必须记录您对UnmodifiableList遵循相同的行为。

开发人员有责任知道他们使用的库是做什么的图书馆创建者有责任使这些信息可用

最重要的是, Iterable被嵌入到语言中( for(a:b)循环),并且尽管您可以创建自己的ReadOnlyIterable接口,但它不会那么强大。

这种方法的问题是您牺牲了编译时的完整性 换句话说,某人可以编译使用remove()方法的代码,直到运行时它才起作用。 这意味着,如果它们碰巧无法正确测试,则直到生产中您才发现不应该使用该方法。

您也许可以使用注释和警告来减轻这种情况-如果它们听警告,或者使用向IDE告知警告的内容,它们会更早地发现。 但是您不能依靠用户来做正确的事 这就是为什么您必须抛出异常而不是什么都不做的原因。

您可以遵循Guava的方法,该方法定义了Iterator的抽象子类,该子类可确保不支持 remove()方法,并且始终会抛出UnsupportedOperationException

这是UnmodifiableIterator的来源。 注意remove()方法是final

public abstract class UnmodifiableIterator<E> implements Iterator<E> {
  /** Constructor for use by subclasses. */
  protected UnmodifiableIterator() {}

  /**
   * Guaranteed to throw an exception and leave the underlying data unmodified.
   *
   * @throws UnsupportedOperationException always
   */
  @Override
  public final void remove() {
    throw new UnsupportedOperationException();
  }
}

现在,在任何不可变的iterable中,您都可以将iterator()方法的返回类型更改为

public Iterator iterator() { ... }

至:

public UnmodifiableIterator iterator() { ... }

这要归功于Java 5中引入的协变返回类型 。现在,这给客户端提供了一个静态类型的信心 ,即迭代器肯定是不可变的。

但是, 您仍然应该为强调此行为的方法提供JavaDoc ,因为如果客户端没有故意寻找返回类型,则返回类型仍然很容易被忽略。

如果您想让您的课程成为可Iterable那就不行。 您必须处理它。 您应该添加JavaDoc,并在使用API​​的开发人员调用remove()时抛出适当的异常。

您可以使用一种替代方法(为了完整性起见,我添加了更多内容作为建议,而不是作为建议)是提供Enumeration 有关详细信息,请参见此处

遗憾的是,您将无法在其上使用新的for循环,但根据定义,它是不可变的。

这是一个例子:

class E implements Enumeration<Integer> {
  int i = 0;

  @Override
  public boolean hasMoreElements() {
    return true;
  }

  @Override
  public Integer nextElement() {
    return ++i;
  }

}

使用try语句,以便在引发错误时可以使用null System.out.print()处理它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM