Effective Java 第三版读书笔记——条款 21:为后代设计接口

在 Java 8 之前,不可能在不破坏现有实现的情况下为接口添加方法。如果向接口添加了一个新方法,现有的实现通常会缺少该方法,从而导致编译时错误。在 Java 8 中加入了默认方法( default method)构造,目的是允许将方法添加到现有的接口。但是增加新的方法到现有的接口是充满风险的。

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

许多新的默认方法被添加到 Java 8 的核心集合接口中,主要是为了方便使用 lambda 表达式。Java 类库的默认方法是高质量的通用实现,在大多数情况下,它们工作正常。但是,编写一个保留了每个可能的实现的所有不变量的默认方法并不总是可能的

例如,考虑在 Java 8 中添加到 Collection 接口的 removeIf 方法。此方法删除给定布尔方法(或 Predicate 函数式接口)返回 true 的所有元素。默认实现被指定为使用迭代器遍历集合,在每个元素上调用 Predicate 方法,并使用迭代器的 remove 方法删除返回 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),它的所有方法在委托给包装集合类之前在一个锁定对象上进行同步。如果这个类与 Java 8 一起使用,它将继承 removeIf 的默认实现,但实际上不能保持类的基本承诺:自动同步每个方法调用。默认实现对同步一无所知,并且不能访问包含锁定对象的属性。如果客户端在另一个线程同时修改集合的情况下调用 SynchronizedCollection 实例上的 removeIf 方法,则可能会导致 ConcurrentModificationException 异常或其他未指定的行为。

在默认方法存在的情况下,接口的现有实现类可能在没有错误或警告的情况下编译,但在运行时会失败。虽然不是非常普遍,但这个问题也不是一个孤立的事件。应该避免使用默认方法向现有的接口添加新的方法,除非这个需要是关键的,在这种情况下,你应该仔细考虑,以确定现有的接口实现是否会被默认的方法实现所破坏。然而,默认方法对于在创建接口时提供标准的方法实现非常有用,以减轻实现接口的任务(条款 20)。

尽管默认方法现在是 Java 平台的一部分,但是非常悉心地设计接口仍然是非常重要的。虽然默认方法可以将方法添加到现有的接口,但这样做有很大的风险。如果一个接口包含一个小缺陷,可能会永远惹怒用户。如果一个接口有严重缺陷,可能会破坏包含它的 API。因此,在发布之前测试每个新接口是非常重要的。虽然在接口被发布后仍然可以修正缺陷,但你不能依赖这一点

猜你喜欢

转载自blog.csdn.net/sky_asd/article/details/86606522