0

我想在 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();
}

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

4

5 回答 5

3

它似乎通过它的方法签名来表明它,但是这些方法被专门记录在说“不,我们不提供这些行为!”

为了实现这一点,您还必须记录您遵循与UnmodifiableList.

开发人员有责任了解他们使用的库做什么,而库创建者有责任提供这些信息

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

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

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

于 2012-12-12T16:12:29.083 回答
2

您可以遵循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();
  }
}

现在,在任何不可变的可迭代对象中,您都可以将iterator()方法的返回类型从

public Iterator iterator() { ... }

到:

public UnmodifiableIterator iterator() { ... }

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

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

于 2012-12-12T16:46:14.517 回答
1

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

于 2012-12-12T16:11:52.343 回答
1

您可以使用的一种替代方法(我添加它是为了完整性而不是作为推荐)是提供一个Enumeration. 有关详细信息,请参见此处

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

这是一个例子:

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

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

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

}
于 2012-12-12T16:34:01.910 回答
-1

使用该try语句,因此当它引发错误时,您可以使用 null 来处理它System.out.print()

于 2012-12-12T16:16:11.220 回答