我想实现一个在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。
答案 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()
处理它。