# 概述
通过本章节的知识点整理,复盘在 `JDK9` 中提供的一些新特性以及新增的一些API接口。
# 目录
[TOC]
# 主要内容
## 0x00:概述
经过4次跳票,历经曲折的 `Java 9` 终于终于在2017年9月21日发布。
从Java 9 这个版本开始, Java 的计划发布周期是 6 个月,下一个 Java 的主版本将于 2018 年 3 月发布,命名为 `Java 18.3`,紧接着再过六个月将发布 Java18.9。
这意味着 `Java` 的更新从传统的以特性驱动的发布周期,转变为以时间驱动的(6 个月为周期)发布模式,并逐步的将 `Oracle JDK` 原商业特性进行开源。
针对企业客户的需求, `Oracle` 将以三年为周期发布长期支持版本(long termsupport) 。
`Java 9` 提供了超过 `150` 项新功能特性,包括备受期待的模块化系统、可交互的 REPL 工具: jshell, JDK 编译工具, Java 公共 API 和私有代码,以及安全增强、扩展提升、性能管理改善等。可以说Java 9是一个庞大的系统工程,完全做了一个整体改变
> Oracle理念:“小步快跑,快速迭代”。
### 新特性概述
- 模块化系统
- jShell命令
- 多版本兼容jar包
- 接口的私有方法
- 钻石操作符的使用升级
- 语法改进: try语句
- String存储结构变更
- 便利的集合特性: of()
- 增强的Stream API
- 全新的HTTP客户端API
- Deprecated的相关API
- javadoc的HTML 5支持
- Javascript引擎升级: Nashorn
- java的动态编译器
## 0x01:JDK和JRE目录结构的改变
`JDK8` 中的目录结构如下

| 目录 | 描述 |
| ------------ | ------------------------------------------------------------ |
| bin 目录 | 包含命令行开发和调试工具, 如javac, jar和javadoc。 |
| include目录 | 包含在编译本地代码时使用的C/C++头文件 |
| lib 目录 | 包含JDK工具的几个JAR和其他类型的文件。 它有一个tools.jar文件, 其中包 含javac编译器的Java类 |
| jre/bin 目录 | 包含基本命令, 如java命令。 在Windows平台上, 它包含系统的运行时动态链 接库(DLL) 。 |
| jre/lib 目录 | 包含用户可编辑的配置文件, 如.properties和.policy文件。 包含几个JAR。 rt.jar文件包含运行时的Java类和资源文件。 |
`JDK 9` 的目录结构

| 目录 | 描述 |
| ------------ | ------------------------------------------------------------ |
| bin 目录 | 包含所有命令。 在Windows平台上, 它继续包含系统的运行时动态链接库。 |
| conf 目录 | 包含用户可编辑的配置文件, 例如以前位于jre\lib目录中的.properties和.policy文件 |
| include 目录 | 包含要在以前编译本地代码时使用的C/C++头文件。 它只存在于JDK中 |
| jmods 目录 | 包含JMOD格式的平台模块。 创建自定义运行时映像时需要它。 它只存在于JDK中 |
| legal 目录 | 包含法律声明 |
| lib 目录 | 包含非Windows平台上的动态链接本地库。 其子目录和文件不应由开发人员直接编辑或使用 |
## 0x02:模块化系统
> 背景
谈到 `Java 9` 大家往往第一个想到的就是 `Jigsaw` 项目。 众所周知, Java 已经发展超过 20 年(95 年最初发布), Java 和相关生态在不断丰富的同时也越来越暴露出一些问题:
- `Java` 运行环境的膨胀和臃肿。 每次 `JVM` 启动的时候,至少会有 `30~60MB`的内存加载,主要原因是JVM需要加载rt.jar,不管其中的类是否被 `classloader`加载,第一步整个jar都会被JVM加载到内存当中去(而模块化可以根据模块的需要加载程
序运行需要的class)
- 当代码库越来越大,创建复杂,盘根错节的“意大利面条式代码”的几率呈指数级的
增长。 不同版本的类库交叉依赖导致让人头疼的问题,这些都阻碍了 `Java` 开发和
运行效率的提升。
- 很难真正地对代码进行封装, 而系统并没有对不同部分(也就是 `JAR` 文件)之间
的依赖关系有个明确的概念。 每一个公共类都可以被类路径之下任何其它的公共
类所访问到,这样就会导致无意中使用了并不想被公开访问的 `API`。
> 概述
本质上讲也就是说, 用模块来管理各个 `package`,通过声明某个 `package` 暴露, ,模块(module)的概念,其实就是 `package` 外再裹一层, 不声明默认就是隐藏。因此,模块化使得代码组织上更安全,因为它可以指定哪些部分可以暴露,哪些部分隐藏。
**实现目标:**
- 模块化的主要目的在于减少内存的开销
- 只须必要模块,而非全部 `jdk` 模块,可简化各种类库和大型应用的开发和维护
- 改进 `Java SE` 平台,使其可以适应不同大小的计算设备
- 改进其安全性,可维护性,提高性能。
> 使用案例
模块将由通常的类和新的模块声明文件(`module-info.java`) 组成。 该文件是位于`java` 代码结构的顶层, 该模块描述符明确地定义了我们的模块需要什么依赖关系,以及哪些模块被外部使用。 在 `exports` 子句中未提及的所有包默认情况下将封装在模块中, 不能在外部使用。
如下图所示,在 `src` 目录下新建一个 `module-info.java`

我们这个工程下现有有两个模块 `java9demo` 和 `java9test`
要想在 `java9demo` 模块中调用 `java9test` 模块下包中的结构, 需要在 `java9test` 的`module-info.java` 中声明:
```java
/**
* @author songhongkang
* @create 2019 下午 11:57
*/
module java9test {
//package we export
exports com.atguigui.bean;
}
```
> exports:控制着哪些包可以被其它模块访问到。 所有不被导出的包默认
> 都被封装在模块里面。
对应在 `java9demo` 模块的 `src` 下创建 `module-info.java` 文件:
```java
/**
* @author songhongkang
* @create 2019 下午 11:51
*/
module java9demo {
requires java9test;
}
```
> requires:指明对其它模块的依赖。
## 0x03:REPL工具:jShell
**产生背景**
- 像 `Python` 和 `Scala` 之类的语言早就有交互式编程环境 `REPL` (read-evaluate-print -loop) , 以交互式的方式对语句和表达式进行求值。 我们只需要输入一些代码,就可以在编译前获得对程序的反馈。 而之前的 `Java` 版本要想执行代码, 必须创建文件、 声明类、 提供测试方法方可实现。
**设计理念**
- 即写即得、 快速运行
**实现目标**
- `Java 9` 中终于拥有了 `REPL`工具: `jShell`。 让 `Java` 可以像脚本语言一样运行,从控制台启动 `jShell`, 利用jShell在没有创建类的情况下直接声明变量,计算表达式,执行语句。即开发时可以在命令行里直接运行 `Java` 的代码,而无需创建`Java`文件,无需跟人解释 ” `public static void main(String[] args)`” 这句废话。
- `jShell` 也可以从文件中加载语句或者将语句保存到文件中。
- `jShell` 也可以是 `tab` 键进行自动补全和自动添加分号。
**基本使用:**
调出 `jShell`

获得帮助

运行一些代码

导入指定的包

创建一个方法

查看已导入的包,默认已经导入如下的所有包: (包含java.lang包)

> 在 JShell 环境下, 语句末尾的“;” 是可选的。 但推荐还是最好加上。 提高代码可读性。
只需按下 Tab 键, 就能自动补全代码

列出当前 `session` 里所有有效的代码片段(当前jShell创建已运行成功的代码)

查看当前 session 下所有创建过的变量

退出 `jShell`

其他的就不一一举例了,可以通过 `/help` 查看 `jShell` 提供了哪些 `API`
## 0x04:语法改进:接口中声明“私有”方法
`Java 8` 中规定接口中的方法除了抽象方法之外, 还可以定义静态方法和默认的方法。 一定程度上, 扩展了接口的功能, 此时的接口更像是一个抽象类。
在 `Java 9` 中, 接口更加的灵活和强大, 连方法的访问权限修饰符都可以声明为`private` 的了, 此时方法将不会成为你对外暴露的API的一部分。
> 使用案例
定义一个 `MyInterface` 接口,在其声明一个私有的方法
```java
interface MyInterface {
void normalInterfaceMethod();
default void methodDefault1() {
init();
}
public default void methodDefault2() {
init();
}
// This method is not part of the public API exposed by MyInterface
private void init() {
System.out.println("默认方法中的通用操作");
}
}
```
接口的实现类
```java
class MyInterfaceImpl implements MyInterface {
@Override
public void normalInterfaceMethod() {
System.out.println("实现接口的方法");
}
}
public class MyInterfaceTest {
public static void main(String[] args) {
MyInterfaceImpl impl = new MyInterfaceImpl();
impl.methodDefault1();
// impl.init();//不能调用
}
}
```
## 0x05:语法改进:钻石操作符的升级(泛型标识)
在 `JDK9` 中我们将能够与匿名实现类共同使用钻石操作符 `<>`(diamond operator) 在`JDK8` 中如下的操作是会报错的:
```java
Comparator<Object> com = new Comparator<>(){
@Override
public int compare(Object o1, Object o2) {
return 0;
}
};
```
编译报错信息: `Cannot use “<>” with anonymous inner classes.`
但是在 `JDK9` 中,上述操作可以正常执行。
## 0x06:语法改进:try接口的语法升级
`JDK8` 中, 可以通过将"资源"定义在 `try()` 内,实现资源的自动关闭, 但是要求执行后必须关闭的所有资源必须在 `try` 子句中初始化, 否则编译不通过。 如下例所示:
```java
try(InputStreamReader reader = new InputStreamReader(System.in)){
//读取数据细节省略
}catch (IOException e){
e.printStackTrace();
}
```
`JDK9` 中, 用资源语句编写 `try` 将更容易, 我们可以在 `try` 子句中使用已经初始
化过的“资源”, 但此时的资源是 `final` 的,不能再次对其进行赋值:
```JAVA
InputStreamReader reader = new InputStreamReader(System.in);
OutputStreamWriter writer = new OutputStreamWriter(System.out);
try (reader; writer) {
//reader是final的,不可再被赋值
//reader = null;
//具体读写操作省略
} catch (IOException e) {
e.printStackTrace();
}
```
> 资源:创建后GC无法自动回收的对象,需要手动调用 .close() 进行回收。
## 0x07:String结构的变更
在 `JDK9` 中 `String` 不再用 `char[]` 来存储,而是改成了 `byte[]` 加上编码标记, 节约
了一些空间。我们通过观察 `String` 类的源码可以看到
```java
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
@Stable
private final byte[] value;
}
```
`StringBuffer` 和 `StringBuilder` 也是如此。
原因是使用 char[] 进行储存,默认占用了 2个字节,而有的数据通常只需要使用 1 个字节进行储存,所以再变更为 byte[] 储存的基础上,还增加了一个编码标记,当储存的内容为 一些特定的编码时,仍然会使用两个字节进行储存,官方的解释如下
> We propose to change the internal representation of the String class from a
> UTF-16 char array to a byte array plus an encoding-flag field. The new String
> class will store characters encoded either as ISO-8859-1/Latin-1 (one byte per
> character), or as UTF-16 (two bytes per character), based upon the contents
> of the string. The encoding flag will indicate which encoding is used
## 0x08:集合工厂方法:快速创建只读集合
要创建一个只读、 不可改变的集合, 必须构造和分配它, 然后添加元素, 最后包装成一个不可修改的集合。
```java
List<String> namesList = new ArrayList <>();
namesList.add("Joe");
namesList.add("Bob");
namesList.add("Bill");
namesList = Collections.unmodifiableList(namesList);
System.out.println(namesList);
```
缺点:我们一下写了五行。 即:它不能表达为单个表达式。
另一个案例如下
```java
List<String> list = Collections.unmodifiableList(Arrays.asList("a", "b", "c"));
Set<String> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a", "b", "c")));
// 如下操作不适用于jdk 8 及之前版本,适用于jdk 9
Map<String, Integer> map = Collections.unmodifiableMap(new HashMap<>() {
{
put("a", 1);
put("b", 2);
put("c", 3);
}
});
map.forEach((k, v) -> System.out.println(k + ":" + v));
```
上述的一些操作,还是有些繁琐,所以`JDK9` 因此引入了 `.of()` 方法, 这使得类似的事情更容易表达。官方的API文档如下

`List firsnamesList = List.of(“Joe”,”Bob”,”Bill”);`
调用集合中静态方法 `of()`, 可以将不同数量的参数传输到此工厂方法中。 此功能
可用于 `Set` 和 `List`, 也可用于 `Map` 的类似形式。 此时得到的集合, 是不可变的:在
创建后, 继续添加元素到这些集合会导致 “`UnsupportedOperationException`” 。
由于 `Java 8` 中接口方法的实现, 可以直接在 `List`, Set和Map的接口内定义这些方法,便于调用。
```java
List<String> list = List.of("a", "b", "c");
Set<String> set = Set.of("a", "b", "c");
Map<String, Integer> map1 = Map.of("Tom", 12, "Jerry", 21, "Lilei", 33,"HanMeimei", 18);
Map<String, Integer> map2 = Map.ofEntries(Map.entry("Tom", 89), Map.entry("Jim", 78), Map.entry("Tim", 98));
```
## 0x09:InputStream的新方法:tranferTo()
`InputStream` 终于有了一个非常有用的方法: `transferTo`,可以用来将数据直接传输到 `OutputStream`,这是在处理原始数据流时非常常见的一种用法,如下示例。
```java
ClassLoader cl = this.getClass().getClassLoader();
try (InputStream is = cl.getResourceAsStream("hello.txt");
OutputStream os = new FileOutputStream("src\\hello1.txt")) {
is.transferTo(os); // 把输入流中的所有数据直接自动地复制到输出流中
} catch (IOException e) {
e.printStackTrace();
}
```
## 0x0A:StreamAPI中新增的4个方法
- Java 的 `Steam API` 是java标准库最好的改进之一, 让开发者能够快速运算,从而能够有效的利用数据并行计算。 `Java 8` 提供的 `Steam` 能够利用多核架构实现声明式的数据处理。
- 在 `Java 9` 中, `Stream` 接口中添加了 4 个新的方法:
- takeWhile
- dropWhile
- ofNullable
- 还有个 iterate 方法的新重载方法,可以让你提供一个 Predicate (判断条件)来指定什么时候结束迭代。
- 除了对 `Stream` 本身的扩展, `Optional` 和 `Stream` 之间的结合也得到了改进。
现在可以通过 `Optional` 的新方法 `stream()` 将一个 `Optional` 对象转换为一个
(可能是空的) `Stream` 对象。
使用案例
- takeWhile(): 返回从开头开始的按照指定规则尽量多的元素
- dropWhile():与 takeWhile 相反,返回剩余的元素。
```java
@Test
public void test1(){
List<Integer> list = Arrays.asList(23, 43, 45, 55, 61, 54, 32, 2, 45, 89, 7);
//takeWhile 返回从开头开始的按照指定规则尽量多的元素
list.stream().takeWhile(x -> x < 60).forEach(System.out::println);
//dropWhile():与 takeWhile 相反,返回剩余的元素。
list.stream().dropWhile(x -> x < 60).forEach(System.out::println);
}
```
- `ofNullable()`:
Java 8 中 `Stream` 不能完全为 `null`, 否则会报空指针异常。 而 Java 9 中的 ofNullable 方法允许我们创建一个单元素 `Stream`, 可以包含一个非空元素, 也可以创建一个空 `Stream`。
```java
// 报NullPointerException
// Stream<Object> stream1 = Stream.of(null);
// System.out.println(stream1.count());
// 不报异常,允许通过
Stream<String> stringStream = Stream.of("AA", "BB", null);
System.out.println(stringStream.count());// 3
// 不报异常,允许通过
List<String> list = new ArrayList<>();
list.add("AA");
list.add(null);
System.out.println(list.stream().count());// 2
// ofNullable():允许值为null
Stream<Object> stream1 = Stream.ofNullable(null);
System.out.println(stream1.count());// 0
Stream<String> stream = Stream.ofNullable("hello world");
System.out.println(stream.count());// 1
```
- `iterate()` 重载的使用
这个 `iterate` 方法的新重载方法, 可以让你提供一个 Predicate (判断条件)来指定什么时候结束迭代。
```java
// 原来的控制终止方式:
Stream.iterate(1, i -> i + 1).limit(10).forEach(System.out::println);
// 现在的终止方式:
Stream.iterate(1, i -> i < 100, i -> i + 1).forEach(System.out::println);
```
## 0x0B:Optionnal获取Stream的方法
`Optional` 类中 `stream()` 的使用
```
List<String> list = new ArrayList<>();
list.add("Tom");
list.add("Jerry");
list.add("Tim");
Optional<List<String>> optional = Optional.ofNullable(list);
Stream<List<String>> stream = optional.stream();
stream.flatMap(x -> x.stream()).forEach(System.out::println);
```
## 0x0C:Javascript引擎升级:Nashorn
`Nashorn` 项目在 `JDK 9` 中得到改进, 它为 Java 提供轻量级的 Javascript 运行时。Nashorn 项目跟随 Netscape 的 `Rhino` 项目, 目的是为了在 `Java` 中实现一个高
性能但轻量级的 `Javascript` 运行时。 `Nashorn` 项目使得 `Java` 应用能够嵌入
`Javascript`。 它在 `JDK 8` 中为 Java 提供一个 Javascript 引擎。
`JDK 9` 包含一个用来解析 `Nashorn` 的 `ECMAScript` 语法树的 API。 这个 API 使得
IDE 和服务端框架不需要依赖 Nashorn 项目的内部实现类, 就能够分析
`ECMAScript` 代码。

【Java进阶】JDK9新特性