第21项:为“后代”设计接口(design interface for posterity)

  在Java 8之前,不可能在不破坏现有实现的情况下向接口添加方法。如果你向接口添加了新方法,现有的【接口实现类】通常会缺少该方法【的实现】,从而导致编译时错误。在Java 8中,添加了默认方法构造[JLS 9.4],目的是允许向现有接口添加方法。但是在现有接口中添加新的方法充满了风险。

  默认方法的声明包括一个默认实现,该实现由实现该接口但未实现默认方法的所有类使用。 虽然向Java添加默认方法可以将方法添加到现有接口,但无法保证这些方法可以在所有预先存在的实现中使用。默认方法是在其实现类不知情或不同意的情况下被“注入(injected)”的。在Java 8之前,这些实现是在默认情况下编写的,它们的接口永远不会获得任何新方法。

  许多新的默认方法被添加到Java 8的核心集合接口中,主要是为了方便lambdas的使用(第6章)。 Java库的默认方法是高质量的通用实现,在大多数情况下,它们工作正常。但是,并不总是可以编写一个可以维护所有约束条件的默认方法的实现。

  例如,考虑removeIf方法,该方法已添加到Java 8中的Collection接口。此方法删除给定布尔函数(或断言(predicate))返回true的所有元素。指定默认实现以使用其迭代器遍历集合,在每个元素上调用断言,并使用迭代器的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。这个类来自Aphche Commons库,类似于java.util中静态工厂Collections.synchronizedCollection返回的类。Apache版本还提供了使用客户端提供的对象进行加锁的能力,而不是集合【加锁的对象不是整个集合】。换句话说,它是一个包装类(第18项),它的所有方法在委托给包装集合之前在加锁对象上同步。

  Apache的SynchronizedCollection类仍在积极维护,但在撰写本文时,它不会覆盖removeIf方法。如果这个类与Java 8一起使用,它将继承removeIf的默认实现,它实际上不能维护类的基本承诺:自动同步每个方法调用。默认实现对同步一无所知,也无法访问包含锁对象的字段。如果客户端在另一个线程并发修改集合时调用SynchronizedCollection实例上的removeIf方法,则可能导致ConcurrentModificationException或其他未指定的行为。

  为了防止在类似的Java平台库实现中发生这种情况,例如Collections.synchronizedCollection返回的包私有类,JDK维护者必须覆盖默认的removeIf实现和其他类似的方法,以便在调用默认实现之前执行必要的同步。不属于Java平台的预先存在的集合实现没有机会随着接口的变化进行类似的改变,有些还没有这样做。

  在存在默认方法的情况下,接口的现有实现可以在没有错误或警告的情况下编译,但在运行时失败。 虽然不是非常普遍,但这个问题也不是一个孤立的事件。在已知添加到Java 8中的集合接口的少数方法易受影响,并且已知少数现有实现受到了影响。

  除非是关键的需求,否则应该避免使用默认方法向现有接口添加新方法,在这种情况下,你应该仔细考虑现有接口实现是否可能被默认方法实现破坏。但是,默认方法对于创建接口时提供标准方法实现非常有用,可以简化实现接口的任务(第20项)。

  还值得注意的是,默认方法并非旨在支持从接口中删除方法或更改现有方法的签名。在不破坏现有客户端的情况下,这些接口更改都不可能实现(Neither of these interface changes is possible without breaking existing clients.)。

  寓意已经很明显了(The moral is clear)。即使默认方法现在是Java平台的一部分,但是谨慎地设计接口仍然非常重要。 虽然默认方法可以向现有接口添加方法,但这样做的风险很大。如果接口包含一个小缺陷,它可能会永远激怒其用户;如果接口严重不足,则可能会使包含它的API失效。

  因此,在发布之前测试每个新接口至关重要。多个程序员应该以不同的方式实现每个接口。至少,你应该针对三种不同的实现。同样重要的是编写多个客户端程序,使用每个新接口的实例来执行各种任务。这将大大有助于确保每个接口满足其所有预期用途。这些步骤将允许你在发布之前发现接口中的缺陷,这时你仍然可以轻松地更正它们。虽然可以在发布接口后纠正一些接口缺陷,但您无法指望它。

第22项:接口只用于定义类型

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值