1. 程式人生 > >[Google Guava] 2.4-集合擴充套件工具類

[Google Guava] 2.4-集合擴充套件工具類

原文連結 譯文連結 譯者:沈義揚,校對:丁一

簡介

有時候你需要實現自己的集合擴充套件。也許你想要在元素被新增到列表時增加特定的行為,或者你想實現一個Iterable,其底層實際上是遍歷資料庫查詢的結果集。Guava為你,也為我們自己提供了若干工具方法,以便讓類似的工作變得更簡單。(畢竟,我們自己也要用這些工具擴充套件集合框架。)

Forwarding裝飾器

針對所有型別的集合介面,Guava都提供了Forwarding抽象類以簡化裝飾者模式的使用。

Forwarding抽象類定義了一個抽象方法:delegate(),你可以覆蓋這個方法來返回被裝飾物件。所有其他方法都會直接委託給delegate()。例如說:ForwardingList.get(int)實際上執行了delegate().get(int)。

通過建立ForwardingXXX的子類並實現delegate()方法,可以選擇性地覆蓋子類的方法來增加裝飾功能,而不需要自己委託每個方法——譯者注:因為所有方法都預設委託給delegate()返回的物件,你可以只覆蓋需要裝飾的方法。

此外,很多集合方法都對應一個”標準方法[standardxxx]”實現,可以用來恢復被裝飾物件的預設行為,以提供相同的優點。比如在擴充套件AbstractList或JDK中的其他骨架類時,可以使用類似standardAddAll這樣的方法。

讓我們看看這個例子。假定你想裝飾一個List,讓其記錄所有新增進來的元素。當然,無論元素是用什麼方法——add(int, E), add(E), 或addAll(Collection)——新增進來的,我們都希望進行記錄,因此我們需要覆蓋所有這些方法。

class AddLoggingList<E> extends ForwardingList<E> {
    final List<E> delegate; // backing list
    @Override protected List<E> delegate() {
        return delegate;
    }
    @Override public void add(int index, E elem) {
        log(index, elem);
        super.add(index, elem);
    }
    @Override public boolean add(E elem) {
        return standardAdd(elem); // 用add(int, E)實現
    }
    @Override public boolean addAll(Collection<? extends E> c) {
        return standardAddAll(c); // 用add實現
    }
}

記住,預設情況下,所有方法都直接轉發到被代理物件,因此覆蓋ForwardingMap.put並不會改變ForwardingMap.putAll的行為。小心覆蓋所有需要改變行為的方法,並且確保裝飾後的集合滿足介面契約。

通常來說,類似於AbstractList的抽象集合骨架類,其大多數方法在Forwarding裝飾器中都有對應的”標準方法”實現。

對提供特定檢視的介面,Forwarding裝飾器也為這些檢視提供了相應的”標準方法”實現。例如,ForwardingMap提供StandardKeySet、StandardValues和StandardEntrySet類,它們在可以的情況下都會把自己的方法委託給被裝飾的Map,把不能委託的宣告為抽象方法。

PeekingIterator

有時候,普通的Iterator介面還不夠。

注意:Iterators.peekingIterator返回的PeekingIterator不支援在peek()操作之後呼叫remove()方法。

舉個例子:複製一個List,並去除連續的重複元素。

List<E> result = Lists.newArrayList();
PeekingIterator<E> iter = Iterators.peekingIterator(source.iterator());
while (iter.hasNext()) {
    E current = iter.next();
    while (iter.hasNext() && iter.peek().equals(current)) {
        //跳過重複的元素
        iter.next();
    }
    result.add(current);
}

傳統的實現方式需要記錄上一個元素,並在特定情況下後退,但這很難處理且容易出錯。相較而言,PeekingIterator在理解和使用上就比較直接了。

AbstractIterator

用一個例子來解釋AbstractIterator最簡單。比方說,我們要包裝一個iterator以跳過空值。

public static Iterator<String> skipNulls(final Iterator<String> in) {
    return new AbstractIterator<String>() {
        protected String computeNext() {
            while (in.hasNext()) {
                String s = in.next();
                if (s != null) {
                    return s;
                }
            }
            return endOfData();
        }
    };
}

你實現了computeNext()方法,來計算下一個值。如果迴圈結束了也沒有找到下一個值,請返回endOfData()表明已經到達迭代的末尾。

注意:AbstractIterator繼承了UnmodifiableIterator,所以禁止實現remove()方法。如果你需要支援remove()的迭代器,就不應該繼承AbstractIterator。

AbstractSequentialIterator

Iterator<Integer> powersOfTwo = new AbstractSequentialIterator<Integer>(1) { // 注意初始值1!
    protected Integer computeNext(Integer previous) {
        return (previous == 1 << 30) ? null : previous * 2;
    }
};

我們在這兒實現了computeNext(T)方法,它能接受前一個值作為引數。

注意,你必須額外傳入一個初始值,或者傳入null讓迭代立即結束。因為computeNext(T)假定null值意味著迭代的末尾——AbstractSequentialIterator不能用來實現可能返回null的迭代器。