JDK9新特性学习

一、前言

  自从Oracle在2017年9月推出JDK 9之后,JDK版本的更新也定为了每半年更新一次,截至目前,JDK 11即将发布正式版。不过目前大部分的生产环境的版本依旧是8的比较多,这里我们来了解下JDK 9及接下来JDK 10在语言上的新功能,这样以后如果哪天要升级版本了,也好又有个印象。

二、JDK 9

1. 交互式REPL:JShell

  REPL,是一种快速运行语句的基于命令行的工具,其他语言基本都有这种交互式的开发环境,比如python等,而在Java的过去版本中,是没有REPL的。如果你想执行一个简单的打印语句,那么我们首先要创建一个有main方法的类,然后通过javac命令进行编译,再然后运行。而JShell的出现则是为了解决这个问题。

JDK9新特性学习_第1张图片
JShell简单测试.png

这样我们平时可以使用JShell来进行一些简单的测试工作,并且JShell也可以通过tab键进行相应的补全操作。

2. 集合框架的工厂方法:of

  在JDK 9之前,如果要创建一个不可变的集合,我们可以通过Collections的unmodifiable开头的方法来进行创建,不过,JDK 9提供了一种更优雅的创建方式:of方法。

在集合框架的基础类型List,Set,Map中都提供了多个重载的of方法,来提供不可变的静态工厂方法,of方法创建的集合要求参数不能为null。

List list = List.of();
Set set = Set.of(1, 2, 3);
Map map = Map.of("green", 1, "red", 2);

不过有关Map,还可以多说一点:

  1. Map的of方法参数,是键值对依次挨着传递参数的;
  2. 感兴趣的可以看下实现源码,底层保存数据是通过一个final类型的Object数组,保存的方式是下标i保存key,而i+1保存key对应的value,当然key和value都是不能为null的,底层通过Objects.requireNonNull方法进行非空判断。
static final class MapN extends AbstractImmutableMap {
@Stable
final Object[] table; // pairs of key, value
@Stable
final int size; // number of pairs

MapN(Object... input) {
    // 奇偶校验,也就是判断key和value是否一一对应
    if ((input.length & 1) != 0) { // implicit nullcheck of input
        throw new InternalError("length is odd");
    }
    size = input.length >> 1;

    int len = EXPAND_FACTOR * input.length;
    len = (len + 1) & ~1; // ensure table is even length
    table = new Object[len];

    for (int i = 0; i < input.length; i += 2) {
        @SuppressWarnings("unchecked")
            K k = Objects.requireNonNull((K)input[i]);
        @SuppressWarnings("unchecked")
            V v = Objects.requireNonNull((V)input[i+1]);
        int idx = probe(k);
        if (idx >= 0) {
            throw new IllegalArgumentException("duplicate key: " + k);
        } else {
            int dest = -(idx + 1);
            table[dest] = k;
            table[dest+1] = v;
        }
    }
}

具体有关不可变静态工厂方法,可参考API文档说明:https://docs.oracle.com/javase/9/docs/api/java/util/List.html#immutable

3. 接口中的私有方法

  JDK 8中接口引入了默认方法和静态方法,在JDK 9中则是引入了私有方法,私有方法是一种有实现的访问修饰符是private的方法,默认方法和静态方法可以共享私有方法,因此在一定程度上这可以避免冗余代码。

public interface MyInterface {

    void normalMethod();

    static void staticMethod() {
        initStatic();
    }

    default void defaultMethod() {
        init();
    }

    private static void initStatic() {
        System.out.println("private static method!");
    }

    private void init() {
        System.out.println("private method");
    }
}

不过需要注意的点:

  1. 私有方法遵循private修饰符属性,无法被覆盖;
  2. 静态方法无法被覆盖,并且静态方法遵循static属性,无法调用非static方法;
4. Stream API新增方法

在使用JDK 8的过程中,我们已经了解到Stream API所带来的便利,而JDK 9则给Stream接口又新增了4个方法:ofNullableiteratetakeWhiledropWhile,我们来看一下。

4.1 ofNullable方法
public static Stream ofNullable(T t) {
    return t == null ? Stream.empty()
                     : StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}

这个比较简单,将单个元素转换为Stream流,如果是null,则返回一个为空的流。

4.2 iterate方法
public static Stream iterate(T seed, Predicate hasNext, UnaryOperator next)

这是JDK 9添加的iterate的重载方法,原有的iterate方法是一种无限的操作,必须要有limit等类似这样的操作来限制大小。而新添加的这个重载方法多了个条件参数,来决定什么时候结束迭代循环,比如说:

// 比如生成等差数列 0 3 6 9 12 15 18 21 24 27
Stream.iterate(0, n -> n + 3).limit(10).forEach(x -> System.out.print(x + " "));
System.out.println("\n----------------");
Stream.iterate(0, n -> n <= 27, n -> n + 3).forEach(x -> System.out.print(x + " "));

其实,可以说:

 Java SE 9's iterate() = Java SE 8's iterate() + Java SE 8's filter()
4.3 takeWhile方法

这个方法用于对Stream流进行一个条件过滤,返回过滤后的流。该方法是一个默认方法。

default Stream takeWhile(Predicate predicate)

先看一个例子:

Stream stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// output: 1 2 3 
stream.takeWhile(x -> x < 4).forEach(i -> System.out.print(i + " "));

System.out.println("\n------------------");

stream = Stream.of(1, 4, 2, 5, 3, 6, 7, 8, 9, 10);
// output: 1
stream.takeWhile(x -> x < 4).forEach(i -> System.out.print(i + " "));

需要注意的是过滤的方式是依次对Stream流的元素进行判断,返回第一次判断结果为false的情况下所过滤的流。

4.4 dropWhile方法

  dropWhile方法也是对Stream流的一个过滤,不过该方法恰好和takeWhile方法过滤到的流反过来,意思是说删掉满足过滤条件的元素,返回剩下的元素,还是拿上个例子来看:

Stream stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// output: 4 5 6 7 8 9 10 
stream.dropWhile(x -> x < 4).forEach(i -> System.out.print(i + " "));

System.out.println("\n------------------");

stream = Stream.of(1, 4, 2, 5, 3, 6, 7, 8, 9, 10);
// output: 4 2 5 3 6 7 8 9 10 
stream.dropWhile(x -> x < 4).forEach(i -> System.out.print(i + " "));

Stream API参考自:Java SE 9: Stream API Improvements

5. Optional类新增方法

Optional类是JDK 8提供的一种优雅的处理null的解决方法,同样,在JDK 9中,Optional类中也增加了几个方法,我们来看一下。

5.1 stream方法
public Stream stream() {
    if (!isPresent()) {
        return Stream.empty();
    } else {
        return Stream.of(value);
    }
}

该方法用于将一个Optional对象转为Stream流,比如说:

Stream stream = Optional.of(1).stream();

Stream> os = ..;
Stream s = os.flatMap(Optional::stream);
5.2 ifPresentOrElse方法
public void ifPresentOrElse(Consumer action, Runnable emptyAction) {
    if (value != null) {
        action.accept(value);
    } else {
        emptyAction.run();
    }
}

和原有的ifPresent方法很像,不过是多了一个else的判断,也可也算是ifPresent方法和orElse方法的组合形式,其实直接看代码就一目了然了。

5.3 or方法
public Optional or(Supplier> supplier) {
    Objects.requireNonNull(supplier);
    if (isPresent()) {
        return this;
    } else {
        @SuppressWarnings("unchecked")
        Optional r = (Optional) supplier.get();
        return Objects.requireNonNull(r);
    }
}

返回一个Optional对象,如果不为空,返回对象本身,如果为空,返回我们通过Supplier指定的Optional 对象。

6. try-resource改进

我们先看代码,再说改进的内容:
JDK 9之前:

void testARM_Before_Java9() throws IOException {
    BufferedReader reader1 = new BufferedReader(new FileReader("journaldev.txt"));
    try (BufferedReader reader2 = reader1) {
        System.out.println(reader2.readLine());
    }
}

JDK9之后的代码:

void testARM_Java9() throws IOException {
    BufferedReader reader1 = new BufferedReader(new FileReader("journaldev.txt"));
    try (reader1) {
        System.out.println(reader1.readLine());
    }
}

在JDK 9之前,try-resource块需要满足以下条件:

  • try块中的资源需要实现java.lang.Autocloseable接口;
  • 资源的类型需要final或者间接是final类型的;
  • 如果资源在Try-With-Resources语句之外声明,我们应该重新引用局部变量(上面的reader1变量);
  • 新创建的局部变量在Try-With-Resources语句中是有效的;

JDK 9的话,如果我们已经有一个在Try-With-Resource语句外面声明为final或有效final的资源,那么我们就不需要再声明一个局部变量。我们可以在Try-With-Resource语句中使用以前创建的变量。

7. @Deprecated注解增强

  在JDK 8和早期版本中,@Deprecated只是一个没有任何方法的标记接口,用于将某一个类,字段,方法,接口等对象标记为废弃状态。
  而在JDK 9中,Oracle对该注解进行了增强,用于提供关于废弃对象的更多信息,并且还提供了扫描jar文件的工具jdeprscan来分析应用程序种使用废弃API的情况。该注解新增了两个参数:sinceforRemoval,我们来简单看下:

String since() default "";

boolean forRemoval() default false;
  1. since,表示该废弃对象废弃的版本;
  2. forRemoval,表示该废弃元素在以后的版本是否会删除,默认是false;
8. Diamond (钻石) 操作符 (<>)

钻石操作符是JDK 7引入的,目的是为了自动识别泛型<> 尖括号内的类型,以避免代码的冗余,进而提高代码的可读性,举个简单的例子:

Map map = new TreeMap();

JDK 7引入钻石操作符后:

Map map2 = new TreeMap<>();

但是在JDK 9之前,钻石操作符的使用有些限制,就是不能用于匿名内部类上,而JDK 9则解决了这个问题:

List list = new ArrayList<>(){
    @Override
    public boolean add(String s) {
        return super.add(s);
    }
};

List list = new ArrayList<>(){};

以上代码在JDK 8是编译不通过的。

9. 响应式流

JDK9中的Flow API对应响应式流规范,响应式流规范是一种事实标准。JEP 266包含了一组最小接口集合,这组接口能捕获核心的异步发布与订阅。希望在未来第三方能够实现这些接口,并且能共享其方式。

java.util.concurrent.Flow包含以下4个接口:

  • Flow.Processor(处理器)
  • Flow.Publisher(发布者)
  • Flow.Subscriber(订阅者)
  • Flow.Subscription(订阅管理器)

这些接口都支持响应式流发布-订阅框架。Java 9也提供了实用类SubmissionPublisher。一个发布者产生一个或多个物品,这些物品由一个或多个消费者消耗。并且订阅者由订阅管理器管理。订阅管理器连接发布者和订阅者。

有关响应式流,推荐阅读:
Java 9 - 说说响应式流
https://www.journaldev.com/20723/java-9-reactive-streams

10. CompletableFuture API 增强

  CompletableFuture 类我们前面说过,是一个Future的增强类,用于解决Future类所不太好解决的问题,比如说结果的获取。而在JDK 9中,Oracle对该类也进行了增强用于支持一些延时和超时的操作,增加了一些常用方法和更好的子类化的操作方式。

11. Java模块化系统-Jigsaw

  模块化是JDK 9中最令人期待也是最重要的功能,模块化和我们原先jar包的形式完全不同,我们都知道原先在JDK 9之前我们都是通过jar包来进行组织的,比如说,我们常用的rt.jar,tools.jar。而引入了模块化系统之后,JDK被重新组织成94个模块,我们常用的代码比如java.util,java.lang等包都被组织到了java.base模块下。

JDK9新特性学习_第2张图片
JDK 9包组织格式.png

JDK9新特性学习_第3张图片
JDK 8包组织格式.png

  通常情况下一个模块包含着一个模块描述符文件(module-info.java)用来指定模块的名字、依赖(需要注意的是每个模块必须显式得声明其依赖)、对外可使用的包(其余的包则对外不可见)、模块提供的服务、模块使用的服务以及允许哪些模块可以对其进行反射等配置信息。这里需要注意下:

模块描述符文件通常位于根目录下;

感兴趣的可以看下java.base模块,几乎包含了我们常用的所有相关包,如果我们要使用其他的模块,在module-info.java进行相应的引入:

module JDK9FirstDemo {
    requires java.sql;
}

模块最终都会打包成 jar 包来分发和使用的,而打包则是通过JDK 9中的JLink来进行,那么模块打包的 jar 包和普通的 jar 包有什么区别呢?模块和普通的 jar 包几乎是一样的,只不过比普通的 jar 包在根目录下多了一个模块描述符文件—— module-info.class而已。

这里没有详细介绍模块化有两点原因:

  • 一是模块化和我们之前的操作方式有很大区别,虽然JDK 提供了兼容方式,但目前还没有大范围的在生产环境上使用;
  • 这里先了解有这么个东西,等哪天模块化普及了,再来仔细学习下。

如果有兴趣的,推荐看下:Java 9 模块化入门,这篇文章大概介绍了模块化的历史及模块化中各个指令的使用。

三、总结

其实这里只列出了JDK 9部分可能常用的语言方法的特性,其实JDK 9还有许多特性:

  • 在JDK8中,可以单独使用下划线作为变量,但JDK 9变量不能被命名为_;
  • GC的改进;
  • Javadoc搜索;
  • 进程API的改进;
  • 统一的JVM日志处理;
  • HTTP 2客户端API;
  • HTML 5风格的javadoc文档;
  • 多分辨率图像API–JEP 251;
  • 废弃Applet API;

如果以后有用到的,再来更新。

本文参考自:Java 9 Features with Examples
如果要查看 JDK 9全部更新,可参考:http://openjdk.java.net/projects/jdk9/
这里把JDK 10的一些更新也发出来,如果有需要,可以参考:https://www.journaldev.com/20395/java-10-features

你可能感兴趣的:(JDK9新特性学习)