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