有時候你需要實(shí)現(xiàn)自己的集合擴(kuò)展。也許你想要在元素被添加到列表時增加特定的行為,或者你想實(shí)現(xiàn)一個 Iterable,其底層實(shí)際上是遍歷數(shù)據(jù)庫查詢的結(jié)果集。Guava 為你,也為我們自己提供了若干工具方法,以便讓類似的工作變得更簡單。(畢竟,我們自己也要用這些工具擴(kuò)展集合框架。)
針對所有類型的集合接口,Guava 都提供了 Forwarding 抽象類以簡化裝飾者模式的使用。
Forwarding 抽象類定義了一個抽象方法:delegate(),你可以覆蓋這個方法來返回被裝飾對象。所有其他方法都會直接委托給 delegate()。例如說:ForwardingList.get(int)實(shí)際上執(zhí)行了 delegate().get(int)。
通過創(chuàng)建 ForwardingXXX 的子類并實(shí)現(xiàn) delegate()方法,可以選擇性地覆蓋子類的方法來增加裝飾功能,而不需要自己委托每個方法——譯者注:因?yàn)樗蟹椒ǘ寄J(rèn)委托給 delegate()返回的對象,你可以只覆蓋需要裝飾的方法。
此外,很多集合方法都對應(yīng)一個”標(biāo)準(zhǔn)方法[standardxxx]”實(shí)現(xiàn),可以用來恢復(fù)被裝飾對象的默認(rèn)行為,以提供相同的優(yōu)點(diǎn)。比如在擴(kuò)展 AbstractList 或 JDK 中的其他骨架類時,可以使用類似 standardAddAll 這樣的方法。
讓我們看看這個例子。假定你想裝飾一個 List,讓其記錄所有添加進(jìn)來的元素。當(dāng)然,無論元素是用什么方法——add(int, E), add(E), 或 addAll(Collection)——添加進(jìn)來的,我們都希望進(jìn)行記錄,因此我們需要覆蓋所有這些方法。
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)實(shí)現(xiàn)
}
@Override public boolean addAll(Collection<? extends E> c) {
return standardAddAll(c); // 用add實(shí)現(xiàn)
}
}
記住,默認(rèn)情況下,所有方法都直接轉(zhuǎn)發(fā)到被代理對象,因此覆蓋 ForwardingMap.put 并不會改變 ForwardingMap.putAll 的行為。小心覆蓋所有需要改變行為的方法,并且確保裝飾后的集合滿足接口契約。
通常來說,類似于 AbstractList 的抽象集合骨架類,其大多數(shù)方法在 Forwarding 裝飾器中都有對應(yīng)的”標(biāo)準(zhǔn)方法”實(shí)現(xiàn)。
對提供特定視圖的接口,F(xiàn)orwarding 裝飾器也為這些視圖提供了相應(yīng)的”標(biāo)準(zhǔn)方法”實(shí)現(xiàn)。例如,F(xiàn)orwardingMap 提供 StandardKeySet、StandardValues 和 StandardEntrySet 類,它們在可以的情況下都會把自己的方法委托給被裝飾的 Map,把不能委托的聲明為抽象方法。
有時候,普通的 Iterator 接口還不夠。
Iterators 提供一個 Iterators.peekingIterator(Iterator)方法,來把 Iterator 包裝為 PeekingIterator,這是 Iterator 的子類,它能讓你事先窺視[[peek()](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/PeekingIterator.html#peek())]到下一次調(diào)用 next()返回的元素。
注意:Iterators.peekingIterator 返回的 PeekingIterator 不支持在 peek()操作之后調(diào)用remove()方法。
舉個例子:復(fù)制一個 List,并去除連續(xù)的重復(fù)元素。
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)) {
//跳過重復(fù)的元素
iter.next();
}
result.add(current);
}
傳統(tǒng)的實(shí)現(xiàn)方式需要記錄上一個元素,并在特定情況下后退,但這很難處理且容易出錯。相較而言, PeekingIterator 在理解和使用上就比較直接了。
實(shí)現(xiàn)你自己的 Iterator?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();
}
};
}
你實(shí)現(xiàn)了 [computeNext()](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/AbstractIterator.html#computeNext())方法,來計算下一個值。如果循環(huán)結(jié)束了也沒有找到下一個值,請返回 endOfData()表明已經(jīng)到達(dá)迭代的末尾。
注意:AbstractIterator 繼承了 UnmodifiableIterator,所以禁止實(shí)現(xiàn) remove()方法。如果你需要支持 remove()的迭代器,就不應(yīng)該繼承 AbstractIterator。
有一些迭代器用其他方式表示會更簡單。AbstractSequentialIterator 就提供了表示迭代的另一種方式。
Iterator<Integer> powersOfTwo = new AbstractSequentialIterator<Integer>(1) { // 注意初始值1!
protected Integer computeNext(Integer previous) {
return (previous == 1 << 30) ? null : previous * 2;
}
};
我們在這兒實(shí)現(xiàn)了 computeNext(T)方法,它能接受前一個值作為參數(shù)。
注意,你必須額外傳入一個初始值,或者傳入 null 讓迭代立即結(jié)束。因?yàn)?computeNext(T)假定 null 值意味著迭代的末尾——AbstractSequentialIterator 不能用來實(shí)現(xiàn)可能返回 null 的迭代器。