原文鏈接
文章也上傳到
(歡迎關(guān)注,歡迎大神提點(diǎn)。)
Item 21 設(shè)計(jì)接口要向后兼容
Java 8之前,在不破壞現(xiàn)有實(shí)現(xiàn)類的情況下,不允許為接口添加方法。如果你擅自給一個(gè)接口添加新的方法,已經(jīng)實(shí)現(xiàn)這個(gè)接口的類就會(huì)因?yàn)槿鄙龠@個(gè)方法的實(shí)現(xiàn)而報(bào)錯(cuò)。在Java 8 中,默認(rèn)方法被設(shè)計(jì)進(jìn)來(lái),其目的是允許為已有的接口添加方法。但是為現(xiàn)有接口添加默認(rèn)方法是充滿風(fēng)險(xiǎn)的。聲明的默認(rèn)方法可以被所有實(shí)現(xiàn)這個(gè)接口的類直接調(diào)用,但是并不保證這些方法可以正常的工作。這個(gè)默認(rèn)方法在實(shí)現(xiàn)類不知情的情況下被注入(injected)到實(shí)現(xiàn)類中。
在Java8中,collection接口中被添加了很多默認(rèn)方法,最主要的便利之處是使用lambdas表達(dá)式(第6章)。像這類Java庫(kù)中的默認(rèn)實(shí)現(xiàn)是高質(zhì)量并通用的,在大多數(shù)情況下,它們都工作的很順暢。但是并不能保證默認(rèn)方法能適應(yīng)所有可變的環(huán)境。
例如,在Java8的Collection接口中添加的removeIf方法,這個(gè)方法會(huì)刪除所有符合被提供的boolean表達(dá)式(或正則表達(dá)式)的元素,然后返回刪除結(jié)果的布爾值。
/**
* Removes all of the elements of this collection that satisfy the given
* predicate. Errors or runtime exceptions thrown during iteration or by
* the predicate are relayed to the caller.
*
* @implSpec
* The default implementation traverses all elements of the collection using
* its {@link #iterator}. Each matching element is removed using
* {@link Iterator#remove()}. If the collection's iterator does not
* support removal then an {@code UnsupportedOperationException} will be
* thrown on the first matching element.
*
* @param filter a predicate which returns {@code true} for elements to be
* removed
* @return {@code true} if any elements were removed
* @throws NullPointerException if the specified filter is null
* @throws UnsupportedOperationException if elements cannot be removed
* from this collection. Implementations may throw this exception if a
* matching element cannot be removed or if, in general, removal is not
* supported.
* @since 1.8
*/
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
這是最通用的一種實(shí)現(xiàn)方法,但是不幸的是,這種實(shí)現(xiàn)在一些現(xiàn)實(shí)情況下可能失敗。例如:在Apache的公共類庫(kù)中的org.apache.commons.collections4.-collection.SynchronizedCollection方法,類似于在java.util中Collections.-synchronizedCollection的靜態(tài)工廠方法:
/**
* @serial include
*/
static class SynchronizedCollection<E> implements Collection<E>, Serializable {
private static final long serialVersionUID = 3053995032091335093L;
final Collection<E> c; // Backing Collection
final Object mutex; // Object on which to synchronize
SynchronizedCollection(Collection<E> c) {
this.c = Objects.requireNonNull(c);
mutex = this;
}
SynchronizedCollection(Collection<E> c, Object mutex) {
this.c = Objects.requireNonNull(c);
this.mutex = Objects.requireNonNull(mutex);
}
public int size() {
synchronized (mutex) {return c.size();}
}
public boolean isEmpty() {
synchronized (mutex) {return c.isEmpty();}
}
public boolean contains(Object o) {
synchronized (mutex) {return c.contains(o);}
}
public Object[] toArray() {
synchronized (mutex) {return c.toArray();}
}
public <T> T[] toArray(T[] a) {
synchronized (mutex) {return c.toArray(a);}
}
public Iterator<E> iterator() {
return c.iterator(); // Must be manually synched by user!
}
public boolean add(E e) {
synchronized (mutex) {return c.add(e);}
}
public boolean remove(Object o) {
synchronized (mutex) {return c.remove(o);}
}
public boolean containsAll(Collection<?> coll) {
synchronized (mutex) {return c.containsAll(coll);}
}
public boolean addAll(Collection<? extends E> coll) {
synchronized (mutex) {return c.addAll(coll);}
}
public boolean removeAll(Collection<?> coll) {
synchronized (mutex) {return c.removeAll(coll);}
}
public boolean retainAll(Collection<?> coll) {
synchronized (mutex) {return c.retainAll(coll);}
}
public void clear() {
synchronized (mutex) {c.clear();}
}
public String toString() {
synchronized (mutex) {return c.toString();}
}
// Override default methods in Collection
@Override
public void forEach(Consumer<? super E> consumer) {
synchronized (mutex) {c.forEach(consumer);}
}
@Override
public boolean removeIf(Predicate<? super E> filter) {
synchronized (mutex) {return c.removeIf(filter);}
}
@Override
public Spliterator<E> spliterator() {
return c.spliterator(); // Must be manually synched by user!
}
@Override
public Stream<E> stream() {
return c.stream(); // Must be manually synched by user!
}
@Override
public Stream<E> parallelStream() {
return c.parallelStream(); // Must be manually synched by user!
}
private void writeObject(ObjectOutputStream s) throws IOException {
synchronized (mutex) {s.defaultWriteObject();}
}
}
和上面使用同步鎖對(duì)象(Object mutex;)不同的是,Apache版本使用的可以是用戶提供的對(duì)象作為鎖對(duì)象,不過(guò)不管怎樣,都是為了替代collection實(shí)現(xiàn)同步的collection實(shí)現(xiàn)的封裝。換句話說(shuō),這其實(shí)就是一個(gè)包裝類(Item 18),在將所有方法代理給collection之前都加上了同步鎖,鎖住了這里的mutex對(duì)象。
Apache的SynchronizedCollection類仍然被積極的維護(hù)著,但它并不是重寫了removeIf方法,假如它結(jié)合Java8使用的話,機(jī)會(huì)默認(rèn)繼承removeIf的實(shí)現(xiàn),這將會(huì)破壞這個(gè)類的基本功能承諾:自動(dòng)的為每一個(gè)方法調(diào)用同步控制。默認(rèn)的方法并沒(méi)有任何同步操作。如果一個(gè)客戶端在其他線程調(diào)用了SynchronizedCollection類的修改collection方法,就可能會(huì)有ConcurrentModificationException或者其他異常出現(xiàn)。
為了避免類似的問(wèn)題在Java平臺(tái)庫(kù)發(fā)生,JDK的維護(hù)者必須重寫默認(rèn)的removeIf和其他類似的方法實(shí)現(xiàn),保證在調(diào)用默認(rèn)實(shí)現(xiàn)之前執(zhí)行必要的同步操作。
有些包含默認(rèn)方法的接口,可能編譯時(shí)可以通過(guò),但是運(yùn)行時(shí)就會(huì)失敗。已知在Java8中有一小部分添加到colletions接口的方法是易受影響的,還有易影響的現(xiàn)有的實(shí)現(xiàn)。
應(yīng)該盡量避免為現(xiàn)有的接口添加默認(rèn)方法,除非這是必要的,此時(shí)你應(yīng)該認(rèn)真思考添加的方法會(huì)不會(huì)對(duì)現(xiàn)有的接口實(shí)現(xiàn)造成不必要的影響。然而,在創(chuàng)建接口時(shí)添加默認(rèn)方法的實(shí)現(xiàn)時(shí)很有用的,可以減輕接口實(shí)現(xiàn)的負(fù)擔(dān)(Item20)。
細(xì)心的設(shè)計(jì)接口是非常重要的,因?yàn)橐粋€(gè)很細(xì)小的錯(cuò)誤都可能會(huì)破壞API甚至永遠(yuǎn)惹怒用戶。
因此,在你公開接口之前要細(xì)心的測(cè)試它,至少你應(yīng)該為它設(shè)計(jì)三種不同的實(shí)現(xiàn),而且要使用實(shí)現(xiàn)接口的對(duì)象執(zhí)行不同的任務(wù)來(lái)測(cè)試,這將大大提高它的安全性。