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

时间:2012-12-12 16:06:33

标签: java immutability

我想实现一个在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,表明它的内部可以通过迭代器上的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();
}

有没有什么方法可以避免暴露这个删除方法,从而让我的类暴露出一个真正不可变的API?理想的是实现像ReadOnlyIterable这样的东西,但是类似的东西似乎没有用Java。

5 个答案:

答案 0 :(得分:3)

它似乎通过其方法签名表明它,但这些方法具体记录为“不,我们不提供这些行为!”

为了实现这一目标,您还必须记录您对UnmodifiableList采取相同的行为。

开发人员负责了解他们使用的库做,以及库创建者制作的责任该信息可用

底线是,Iterable被烘焙到语言(for(a:b)循环)中,虽然您可以创建自己的ReadOnlyIterable界面,但它不会那么健壮。

这种方法的问题在于您牺牲编译时完整性。换句话说,有人可以编译使用remove()方法的代码,并且在运行时它们不会发现它不起作用。这意味着如果它们碰巧没有正确测试,那么在生产中你不会发现不应该使用该方法。

您也许可以使用注释和警告来缓解这种情况 - 如果他们听警告,或使用告诉他们警告的IDE,他们会更早发现。但你不能依靠用户做正确的事。这就是为什么你必须抛出异常而不是什么都不做。

答案 1 :(得分:2)

您可以按照Guava的方法,即定义Iterator的抽象子类,以确保remove()方法不受支持并始终扔一个UnsupportedOperationException

这是UnmodifiableIterator的来源。请注意,remove()方法最终

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中引入的covariant return types。现在,这为客户端提供了一个静态类型的置信度,迭代器肯定是不可变的。

但是,你仍然应该为强调这种行为的方法提供一个JavaDoc ,因为如果客户端没有故意查找返回类型,仍然很容易被忽略。

答案 2 :(得分:1)

如果你想让你的班级成为Iterable,那么不。你必须处理它。您应该添加JavaDoc,并在使用API​​调用remove()的开发人员时抛出正确的异常。

答案 3 :(得分:1)

您可以使用的一个替代方案(我将其添加为完整性而不是推荐)是提供Enumeration。有关详细信息,请参阅here

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

以下是一个例子:

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

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

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

}

答案 4 :(得分:-1)

使用try语句,因此当它抛出错误时,您可以使用null System.out.print()处理它。