【JAVA高级&JDK9新特性】知识点整理

【JAVA高级&JDK9新特性】知识点整理

概述

通过本章节的知识点整理,复盘在 JDK9 中提供的一些新特性以及新增的一些API接口。

目录

主要内容

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

我们这个工程下现有有两个模块 java9demojava9test

要想在 java9demo 模块中调用 java9test 模块下包中的结构, 需要在 java9testmodule-info.java 中声明:

/**
* @author songhongkang
* @create 2019 下午 11:57
*/
module java9test {
    //package we export
    exports com.atguigui.bean;
}

exports:控制着哪些包可以被其它模块访问到。 所有不被导出的包默认
都被封装在模块里面。

对应在 java9demo 模块的 src 下创建 module-info.java 文件:

/**
* @author songhongkang
* @create 2019 下午 11:51
*/
module java9demo {
    requires java9test;
}

requires:指明对其它模块的依赖。

0x03:REPL工具:jShell

产生背景

  • PythonScala 之类的语言早就有交互式编程环境 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 接口,在其声明一个私有的方法

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("默认方法中的通用操作");
    }
}

接口的实现类

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 中如下的操作是会报错的:

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 子句中初始化, 否则编译不通过。 如下例所示:

try(InputStreamReader reader = new InputStreamReader(System.in)){
    //读取数据细节省略
}catch (IOException e){
    e.printStackTrace();
}

JDK9 中, 用资源语句编写 try 将更容易, 我们可以在 try 子句中使用已经初始
化过的“资源”, 但此时的资源是 final 的,不能再次对其进行赋值:

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结构的变更

JDK9String 不再用 char[] 来存储,而是改成了 byte[] 加上编码标记, 节约
了一些空间。我们通过观察 String 类的源码可以看到

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    @Stable
    private final byte[] value;
}

StringBufferStringBuilder 也是如此。

原因是使用 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:集合工厂方法:快速创建只读集合

要创建一个只读、 不可改变的集合, 必须构造和分配它, 然后添加元素, 最后包装成一个不可修改的集合。

List<String> namesList = new ArrayList <>();
namesList.add("Joe");
namesList.add("Bob");
namesList.add("Bill");
namesList = Collections.unmodifiableList(namesList);
System.out.println(namesList);

缺点:我们一下写了五行。 即:它不能表达为单个表达式。

另一个案例如下

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(), 可以将不同数量的参数传输到此工厂方法中。 此功能
可用于 SetList, 也可用于 Map 的类似形式。 此时得到的集合, 是不可变的:在
创建后, 继续添加元素到这些集合会导致 “UnsupportedOperationException” 。
由于 Java 8 中接口方法的实现, 可以直接在 List, Set和Map的接口内定义这些方法,便于调用。

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,这是在处理原始数据流时非常常见的一种用法,如下示例。

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 本身的扩展, OptionalStream 之间的结合也得到了改进。
    现在可以通过 Optional 的新方法 stream() 将一个 Optional 对象转换为一个
    (可能是空的) Stream 对象。

使用案例

  • takeWhile(): 返回从开头开始的按照指定规则尽量多的元素
  • dropWhile():与 takeWhile 相反,返回剩余的元素。
@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

// 报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 (判断条件)来指定什么时候结束迭代。

// 原来的控制终止方式:
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 包含一个用来解析 NashornECMAScript 语法树的 API。 这个 API 使得
IDE 和服务端框架不需要依赖 Nashorn 项目的内部实现类, 就能够分析
ECMAScript 代码。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://codeyee.com/archives/java-jdk9.html