1.5 默认方法及Java模块

正如前文所介绍的,现代系统倾向于基于组件进行构建,而这些组件可能源自第三方。历史上,Java对此的支持非常薄弱,它只支持由几个Java包组成的JAR文件,并且这些Java包也没什么结构。此外,要演进这些包中的接口也比较困难——改动一个Java接口时,实现该接口的所有类都会受影响。Java 8和Java 9已经开始着手改进这一问题。

首先,Java 9提供了模块系统,允许你通过语法定义由一系列包组成的模块——通过它你能更好地控制命名空间和包的可见性。模块对简单的类JAR组件进行了增强,使其具备了结构,既能作为用户文档,也能由机器进行检查。第14章会详细探讨这部分内容。其次,Java 8引入了默认方法来支持接口的演进。第13章会详细介绍默认方法。它们非常重要,因为使用接口时你经常会碰到,然而大多数的程序员可能并不需要编写默认方法,因为默认方法只是推进程序演进的一种技术,并不会直接帮助你实现某个特性。本节会基于一个例子简要地介绍默认方法。

1.4节中给出了下面这段Java 8示例代码:

  1. List<Apple> heavyApples1 =
  2. inventory.stream().filter((Apple a) -> a.getWeight() > 150)
  3. .collect(toList());
  4. List<Apple> heavyApples2 =
  5. inventory.parallelStream().filter((Apple a) -> a.getWeight() > 150)
  6. .collect(toList());

但这里有个问题:在Java 8之前,List并没有streamparallelStream方法,它实现的Collection接口也没有,因为当初还没有想到这些方法嘛!可没有这些方法,这些代码就不能编译。换作你自己的接口的话,最简单的解决方案就是让Java 8的设计者把stream方法加入Collection接口,并加入ArrayList类的实现。

可要是这样做,对用户来说就是噩梦了。有很多集合框架都用Collection API实现了接口。但给接口加入一个新方法,意味着所有的实体类都必须为其提供一个实现。语言设计者没法控制Collection所有现有的实现,这下你就进退两难了:你如何改变已发布的接口而不破坏已有的实现呢?

Java 8的解决方法就是打破最后一环——接口如今可以包含实现类没有提供实现的方法签名了!那谁来实现它呢?缺失的方法主体随接口提供了(因此就有了默认实现),而不是由实现类提供。

这就给接口设计者提供了一种扩充接口的方式,而不会破坏现有的代码。Java 8在接口声明中使用新的default关键字来表示这一点。

例如,在Java 8里,你可以直接对List调用sort方法。它是用Java 8 List接口中如下所示的默认方法实现的,它会调用Collections.sort静态方法:

  1. default void sort(Comparator<? super E> c) {
  2. Collections.sort(this, c);
  3. }

这意味着List的任何实体类都不需要显式实现sort,而在以前的Java版本中,除非提供了sort的实现,否则这些实体类在重新编译时都会失败。

不过请稍等,一个类可以实现多个接口,不是吗?那么,如果在好几个接口里有多个默认实现,是否意味着Java中有了某种形式的多重继承?是的,在某种程度上是这样。第13章中会谈到,Java 8用一些限制来避免出现类似于C++中臭名昭著的菱形继承问题