Effective-java 3 中文翻譯系列 (Item 21 設(shè)計(jì)接口要向后兼容)

原文鏈接

文章也上傳到

github

(歡迎關(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è)試,這將大大提高它的安全性。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容