Java 杂谈

Effective-java 3 中文翻译系列 (Item 21

2018-07-14  本文已影响72人  TryEnough

文章也上传到

github

(欢迎关注,欢迎大神提点。)


Item 21 设计接口要向后兼容


Java 8之前,在不破坏现有实现类的情况下,不允许为接口添加方法。如果你擅自给一个接口添加新的方法,已经实现这个接口的类就会因为缺少这个方法的实现而报错。在Java 8 中,默认方法被设计进来,其目的是允许为已有的接口添加方法。但是为现有接口添加默认方法是充满风险的。声明的默认方法可以被所有实现这个接口的类直接调用,但是并不保证这些方法可以正常的工作。这个默认方法在实现类不知情的情况下被注入(injected)到实现类中。

在Java8中,collection接口中被添加了很多默认方法,最主要的便利之处是使用lambdas表达式(第6章)。像这类Java库中的默认实现是高质量并通用的,在大多数情况下,它们都工作的很顺畅。但是并不能保证默认方法能适应所有可变的环境。

例如,在Java8的Collection接口中添加的removeIf方法,这个方法会删除所有符合被提供的boolean表达式(或正则表达式)的元素,然后返回删除结果的布尔值。

    /**
     * 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;
    }

这是最通用的一种实现方法,但是不幸的是,这种实现在一些现实情况下可能失败。例如:在Apache的公共类库中的org.apache.commons.collections4.-collection.SynchronizedCollection方法,类似于在java.util中Collections.-synchronizedCollection的静态工厂方法:

    /**
     * @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();}
        }
    }

和上面使用同步锁对象(Object mutex;)不同的是,Apache版本使用的可以是用户提供的对象作为锁对象,不过不管怎样,都是为了替代collection实现同步的collection实现的封装。换句话说,这其实就是一个包装类(Item 18),在将所有方法代理给collection之前都加上了同步锁,锁住了这里的mutex对象。
Apache的SynchronizedCollection类仍然被积极的维护着,但它并不是重写了removeIf方法,假如它结合Java8使用的话,机会默认继承removeIf的实现,这将会破坏这个类的基本功能承诺:自动的为每一个方法调用同步控制。默认的方法并没有任何同步操作。如果一个客户端在其他线程调用了SynchronizedCollection类的修改collection方法,就可能会有ConcurrentModificationException或者其他异常出现。
为了避免类似的问题在Java平台库发生,JDK的维护者必须重写默认的removeIf和其他类似的方法实现,保证在调用默认实现之前执行必要的同步操作。

有些包含默认方法的接口,可能编译时可以通过,但是运行时就会失败。已知在Java8中有一小部分添加到colletions接口的方法是易受影响的,还有易影响的现有的实现。

应该尽量避免为现有的接口添加默认方法,除非这是必要的,此时你应该认真思考添加的方法会不会对现有的接口实现造成不必要的影响。然而,在创建接口时添加默认方法的实现时很有用的,可以减轻接口实现的负担(Item20)。

细心的设计接口是非常重要的,因为一个很细小的错误都可能会破坏API甚至永远惹怒用户。

因此,在你公开接口之前要细心的测试它,至少你应该为它设计三种不同的实现,而且要使用实现接口的对象执行不同的任务来测试,这将大大提高它的安全性。

上一篇下一篇

猜你喜欢

热点阅读