簡體   English   中英

如何實現可迭代的類並公開真正的不可變的API?

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

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM