[英]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.