领域修炼之路

JAVA 9中的9个新特性

Java 8在发布三年多之后,下一个版本Java 9即将在2017年7月发布。你或许已经听说过Java 9的模块系统,下面来看一下将与Java 9一起提供的九个令人兴奋的新功能。

1. Java平台模块系统

Java平台的模块化(JSR 376)之路,从与OSGi的模块化标准(JSR 291)之争,再到功能实现从Java 7推迟到Java 8再到Java 9的一拖再拖,可谓历经坎坷。模块系统主要解决两个基本问题:1)以类和接口进行面向对象的设计在系统规模越来越大时因为隔离粒度问题而难以真正地封装代码,因为在更高粒度层面(JAR文件),系统的不同部分之间没有明确的依赖关系的概念。2)类路径中的任何其他类可以访问每个公共类,这会导致开发人员无意中使用不是公开API的类,严重影响系统后续维护升级。此外,类路径本身也是有问题的:开发人员无法知道所有必需的JAR是否在那里,或者是否有重复的条目。现在,Java 9系统模块化标准解决了这两个问题。

模块化的JAR文件包含了一个附加的模块描述符。在这个模块描述符中,通过requires语句来表示对其他模块的依赖,此外,exports语句可以控制哪些package可被其他模块使用。默认情况下,所有未导出的package都封装在模块中不被其他模块可见。下述是定义在module-info.java中的模块描述符的一个例子:

1
2
3
4
5
module blog {
exports com.pluralsight.blog;
requires cms;
}

可以通过下图展示两个模块blog和cms之间的关系:

注意,上述两个模块都包含没有被导出(上图中使用橙色屏蔽)的内部封装的包。这样可以避免开发人员不小心使用这些包中的类。 Java平台本身也使用自己的模块系统进行了模块化。通过封装JDK内部类,Java平台变得更加安全并且让后续的持续演进变得容易得多。

当启动模块化应用程序时,JVM会根据“require”语句(比起类路径解析,粒度上上升了一个层面)来验证是否可以解析所有模块。模块化可以让开发人员通过强大的封装和显式依赖性来更好地构建大型应用。

2. 装配(Linking)

当可以定义显式依赖关系的模块和模块化的JDK出现时,应用的构建会出现新的可能性。既然应用程序模块可以声明其对其他应用程序模块以及JDK使用的模块的依赖,为什么不使用这些依赖信息创建一个最小的运行时环境,只包含运行应用程序所需的那些模块?这可以通过Java 9中的新的jlink工具实现。通过创建针对应用程序进行优化的最小运行时镜像,开发人员将不再需要使用完全加载的JDK安装环境来运行应用程序。

3. JShell:Java交互式REPL

许多语言已经具有交互式的Read-Eval-Print-Loop,Java现在加入了这个俱乐部。开发人员可以从控制台启动jshell,然后直接输入和执行Java代码。 jshell的即时反馈使它成为探索API和尝试语言特性的好工具。

4. 改进的Javadoc

Javadoc现在提供了API文档中的搜索功能。Javadoc的输出现在也符合HTML5标准。此外,你会注意到,每个Javadoc页面都包含有关JDK模块类或接口来源的信息。

5. 集合类的工厂方法

以前,在代码中创建并初始化一个集合(例如,List或Set),需要实例化一个集合类的具体实现,然后通过调用“add”方法进行元素填充。这会导致重复的代码。Java 9添加了几种集合工厂方法来简化上述需求:

1
2
Set<Integer> ints = Set.of(1, 2, 3);
List<String> strings = List.of("first", "second");

除了更简洁和更好阅读之外,这些方法还可以让开发人员不必为选择特定的集合类实现而“头痛”。事实上,从工厂方法返回的集合实现是根据初始化时输入的元素数量高度优化过的。Java编译器能实现这一点是因为初始化的集合实例是不可变(immutable)的:在创建后,如果向集合实例添加新的元素项会导致“UnsupportedOperationException”。

6. Stream API改进

Streams API可以说是Java长期演进过程中对标准库的最佳改进之一。它允许开发人员以声明式管道的方式对集合进行转换。在Java 9中,Stream接口中添加了几种新方法,如:dropWhiletakeWhileofNullable。此外,迭代方法添加了一个新的重载,允许开发人员提供一个谓词(Predicate)来停止迭代:

1
IntStream.iterate(1, i -> i < 100, i -> i + 1).forEach(System.out::println);

上述示例中,第二个参数是一个lambda,直到IntStream中的当前元素变为100时返回true。

除了Stream本身的这些增加之外,OptionStream之间的集成也得到增强。现在可以使用Option中新的“stream”方法将Option对象转换为Stream(可能为空):

1
Stream<Integer> s = Optional.of(1).stream();

Option转换为Stream在组合复杂的Stream管道时尤其有用。

7. 私有接口方法

Java 8支持在接口添加默认方法,因此,接口现在也可以包含行为,而不仅仅是方法签名。可是,如果在接口中有几个默认方法,代码几乎相同,会发生什么?通常,我们需要重构这些方法来调用包含共享功能的私有方法。现在的问题是:默认方法不能是私有的。使用共享代码创建另一个默认方法不是一个可行的解决方案,因为该帮助方法会成为公共API的一部分。在Java 9中,可以向接口添加私有的帮助方法来解决此问题:

1
2
3
4
5
6
7
8
9
10
11
public interface MyInterface {
void normalInterfaceMethod();
default void interfaceMethodWithDefault() { init(); }
default void anotherDefaultMethod() { init(); }
// This method is not part of the public API exposed by MyInterface
private void init() { System.out.println("Initializing"); }
}

8. HTTP/2

Java 9提供了一种HTTP调用的新方式。对于旧的“HttpURLConnetion”API来说,这个迟来的替换也添加了对WebSockets和HTTP/2的支持。注意:新的HttpClient API在Java 9中作为所谓的孵化器模块提供,这意味着API不能100%的保证是最终版本。不过,随着Java 9的到来,开发人员总是可以开始使用这个API了:

1
2
3
4
5
6
7
8
9
10
HttpClient client = HttpClient.newHttpClient();
HttpRequest req =
HttpRequest.newBuilder(URI.create("http://www.google.com"))
.header("User-Agent","Java")
.GET()
.build();
HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandler.asString());

除了这个简单的请求/响应模型,HttpClient提供了新的API来处理HTTP/2功能,如流和服务器推送。

9. 多版本JAR

需要强调的最后一个功能对于类库维护者来说特别好。当新版本的Java出来时,某个类库的所有用户通常需要几年时间才能切换到这个新版本。这意味着类库必须向后兼容需要支持的最旧版本的Java(例如,在许多情况下为Java 6或7)。这实际上意味着很长一段时间内类库开发者无法在代码中使用Java 9的新功能。幸运的是,多版本JAR功能允许开发人员创建仅在特定Java版本上运行库时使用的类的备用版本:

1
2
3
4
5
6
7
8
9
multirelease.jar
├── META-INF
│ └── versions
│ └── 9
│ └── multirelease
│ └── Helper.class
├── multirelease
├── Helper.class
└── Main.class

在上述示例中,multirelease.jar可以在Java 9上使用,调用Helper类时,JVM会使用“META-INF/versions/9”下的Helper类,而不是顶级的multirelease.Helper类。该类的Java 9特定版本可以使用Java 9功能和类库。同时,早期版本的Java虚拟机环境中此JAR仍然可以使用,因为较旧的JVM版本只能看到顶级的Helper类。

原文链接

chrisrc wechat
更多信息请订阅我的微信订阅号