Effective Java 第三版——21. 为后代设计接口

Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必不少人都读过,号称Java四大名著之一,不过第二版2009年出版,到如今已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深入的变化。
在这里第一时间翻译成中文版。供你们学习分享之用。java

Effective Java, Third Edition

21. 为后代设计接口

在Java 8以前,不可能在不破坏现有实现的状况下为接口添加方法。 若是向接口添加了一个新方法,现有的实现一般会缺乏该方法,从而致使编译时错误。 在Java 8中,添加了默认方法( default method)构造[JLS 9.4],目的是容许将方法添加到现有的接口。 可是增长新的方法到现有的接口是充满风险的。程序员

默认方法的声明包含一个默认实现,该方法容许实现接口的类直接使用,而没必要实现默认方法。 虽然在Java中添加默认方法能够将方法添加到现有接口,但不能保证这些方法能够在全部已有的实现中使用。 默认的方法被“注入(injected)”到现有的实现中,没有通过实现类的知道或赞成。 在Java 8以前,这些实现是用默认的接口编写的,它们的接口永远不会得到任何新的方法。apache

许多新的默认方法被添加到Java 8的核心集合接口中,主要是为了方便使用lambda表达式(第6章)。 Java类库的默认方法是高质量的通用实现,在大多数状况下,它们工做正常。 可是,编写一个默认方法并不老是可能的,它保留了每一个可能的实现的全部不变量函数

例如,考虑在Java 8中添加到Collection接口的removeIf方法。例如,考虑在Java 8中添加到Collection接口的removeIf方法。此方法删除给定布尔方法(或Predicate函数式接口)返回true的全部元素。默认实现被指定为使用迭代器遍历集合,调用每一个元素的谓词,并使用迭代器的remove方法删除谓词返回true的元素。 据推测,这个声明看起来像这样:默认实现被指定为使用迭代器遍历集合,调用每一个元素的Predicate函数式接口,并使用迭代器的remove方法删除Predicate函数式接口返回true的元素。 根据推测,这个声明看起来像这样:学习

// Default method added to the Collection interface in Java 8

default boolean removeIf(Predicate<? super E> filter) {

    Objects.requireNonNull(filter);

    boolean result = false;

    for (Iterator<E> it = iterator(); it.hasNext(); ) {

        if (filter.test(it.next())) {

            it.remove();

            result = true;

        }

    }

    return result;

}

这是可能为removeIf方法编写的最好的通用实现,但遗憾的是,它在一些实际的Collection实现中失败了。 例如,考虑org.apache.commons.collections4.collection.SynchronizedCollection方法。 这个类出自Apache Commons类库中,与java.util包中的静态工厂Collections.synchronizedCollection方法返回的类类似。 Apache版本还提供了使用客户端提供的对象进行锁定的能力,以代替集合。 换句话说,它是一个包装类(条目 18),它们的全部方法在委托给包装集合类以前在一个锁定对象上进行同步。测试

Apache的SynchronizedCollection类仍然在积极维护,但在撰写本文时,并未重写removeIf方法。 若是这个类与Java 8一块儿使用,它将继承removeIf的默认实现,但实际上不能保持类的基本承诺:自动同步每一个方法调用。 默认实现对同步一无所知,而且不能访问包含锁定对象的属性。 若是客户端在另外一个线程同时修改集合的状况下调用SynchronizedCollection实例上的removeIf方法,则可能会致使ConcurrentModificationException异常或其余未指定的行为。ui

为了防止在相似的Java平台类库实现中发生这种状况,好比Collections.synchronizedCollection返回的包级私有的类,JDK维护者必须重写默认的removeIf实现和其余相似的方法来在调用默认实现以前执行必要的同步。 原来不属于Java平台的集合实现没有机会与接口更改进行相似的改变,有些尚未这样作。线程

在默认方法的状况下,接口的现有实现类能够在没有错误或警告的状况下编译,但在运行时会失败。 虽然不是很是广泛,但这个问题也不是一个孤立的事件。 在Java 8中添加到集合接口的一些方法已知是易受影响的,而且已知一些现有的实现会受到影响。翻译

应该避免使用默认方法向现有的接口添加新的方法,除非这个须要是关键的,在这种状况下,你应该仔细考虑,以肯定现有的接口实现是否会被默认的方法实现所破坏。然而,默认方法对于在建立接口时提供标准的方法实现很是有用,以减轻实现接口的任务(条目 20)。设计

还值得注意的是,默认方法不是被用来设计,来支持从接口中移除方法或者改变现有方法的签名的目的。在不破坏现有客户端的状况下,这些接口都不可能发生更改。

准则是清楚的。 尽管默认方法如今是Java平台的一部分,可是很是悉心地设计接口仍然是很是重要的。 虽然默认方法能够将方法添加到现有的接口,但这样作有很大的风险。 若是一个接口包含一个小缺陷,可能会永远惹怒用户。 若是一个接口严重缺陷,可能会破坏包含它的API。

所以,在发布以前测试每一个新接口是很是重要的。 多个程序员应该以不一样的方式实现每一个接口。 至少,你应该准备三种不一样的实现。 编写多个使用每一个新接口的实例来执行各类任务的客户端程序一样重要。 这将大大确保每一个接口都能知足其全部的预期用途。 这些步骤将容许你在发布以前发现接口中的缺陷,但仍然能够轻松地修正它们。 虽然在接口被发布后可能会修正一些存在的缺陷,但不要太期望这一点

相关文章
相关标签/搜索