你好,我是看山。

本文收录在 《从小工到专家的 Java 进阶之旅》 系列专栏中。

从 2017 年开始,Java 版本更新策略从原来的每两年一个新版本,改为每六个月一个新版本,以快速验证新特性,推动 Java 的发展。从 《JVM Ecosystem Report 2021》 中可以看出,目前开发环境中有近半的环境使用 Java8,有近半的人转移到了 Java11,随着 Java17 的发布,相信比例会有所变化。

因此,准备出一个系列,配合示例讲解,阐述各个版本的新特性。

概述

相较于 Java8,Java9 没有新增语法糖,但是其增加的特性也都是非常实用的,比如 Jigsaw 模块化、JShell、发布-订阅框架、GC 等。本文将快速、高层次的介绍一些新特性,完整的特性可以参加https://openjdk.java.net/projects/jdk9/。

这里需要说明一下,由于 Java9 并不是长期支持版,当前也是从现在看过去,所以笔者偷个懒,文章的示例代码都是在 Java11 下写的,可能会与 Java9 中的定义有些出入,不过,这也没啥,毕竟我们真正使用的时候还是优先考虑长期支持版。

Jigsaw 模块化

模块化是一个比较大的更新,这让以前 All-in-One 的 Java 包拆分成几个模块。这种模块化系统提供了类似 OSGi 框架系统的功能,比如多个模块可以独立开发,按需引用、按需集成,最终组装成一个完整功能。

模块具有依赖的概念,可以导出功能 API,可以隐藏实现细节。

还有一个好处是可以实现 JVM 的按需使用,能够减小 Java 运行包的体积,让 JVM 在内存更小的设备上运行。JVM 当时的初衷就是做硬件,也算是不忘初心了。

另外,JVM 中com.sun.*的之类的内部 API,做了更强的封闭,不在允许调用,提升了内核安全。

在使用的时候,我们需要在 java 代码的顶层目录中定义一个module-info.java文件,用于描述模块信息:

module cn.howardliu.java9.modules.car {requires cn.howardliu.java9.modules.engines;exports cn.howardliu.java9.modules.car.handling;
}

上面描述的信息是:模块cn.howardliu.java9.modules.car需要依赖模块cn.howardliu.java9.modules.engines,并导出模块cn.howardliu.java9.modules.car.handling

更多的信息可以查看 OpenJDK 的指引 https://openjdk.java.net/projects/jigsaw/quick-start,后续会单独介绍 Jigsaw 模块的使用,内容会贴到评论区。

全新的 HTTP 客户端

这是一个千呼万唤始出来的功能,终于有官方 API 可以替换老旧难用的HttpURLConnection。只不过,在 Java9 中,新版 HTTP 客户端是放在孵化模块中(具体信息可以查看 https://openjdk.java.net/jeps/110)。

老版 HTTP 客户端存在很多问题,大家开发的时候基本上都是使用第三方 HTTP 库,比如 Apache HttpClient、Netty、Jetty 等。

新版 HTTP 客户端的目标很多,毕竟这么多珠玉在前,如果还是做成一坨,指定是要被笑死的。所以新版 HTTP 客户端列出了 16 个目标,包括简单易用、打印关键信息、WebSocket、HTTP/2、HTTPS/TLS、良好的性能、非阻塞 API 等等。

我们先简单的瞅瞅:

final String url = "https://postman-echo.com/get";
final HttpRequest request = HttpRequest.newBuilder().uri(new URI(url)).GET().build();final HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());final HttpHeaders headers = response.headers();
headers.map().forEach((k, v) -> System.out.println(k + ":" + v));System.out.println(response.statusCode());
System.out.println(response.body());

新版 HTTP 客户端可以在 Java11 中正常使用了,上面的代码也是在 Java11 中写的,API 是在java.net.http包中。

改进的进程 API

在 Java9 中提供的进程 API,可以控制和管理操作系统进程。也就是说,可以在代码中管理当前进程,甚至可以销毁当前进程。

进程信息

这个功能是由java.lang.ProcessHandle提供的,我们来瞅瞅怎么用:

final ProcessHandle self = ProcessHandle.current();
final long pid = self.pid();
System.out.println("PID: " + pid);final ProcessHandle.Info procInfo = self.info();procInfo.arguments().ifPresent(x -> {for (String s : x) {System.out.println(s);}
});procInfo.commandLine().ifPresent(System.out::println);
procInfo.startInstant().ifPresent(System.out::println);
procInfo.totalCpuDuration().ifPresent(System.out::println);

java.lang.ProcessHandle.Info中提供了丰富的进程信息

销毁进程

我们还可以使用java.lang.ProcessHandle#destroy方法销毁进程,我们演示一下销毁子进程:

ProcessHandle.current().children().forEach(procHandle -> {System.out.println(procHandle.pid());System.out.println(procHandle.destroy());});

从 Java8 之后,我们会发现 Java 提供的 API 使用了OptionalStream等功能,**Eating your own dog food **也是比较值得学习的。

其他小改动

Java9 中还对做了对已有功能做了点改动,我们来瞅瞅都有哪些。

改进 try-with-resources

从 Java7 开始,我们可以使用try-with-resources语法自动关闭资源,所有实现了java.lang.AutoCloseable接口,可以作为资源。但是这里会有一个限制,就是每个资源需要声明一个新变量。

也就是这样:

public static void tryWithResources() throws IOException {try (FileInputStream in2 = new FileInputStream("./")) {// do something}
}

对于这种直接使用的还算方便,但如果是需要经过一些列方法定义的呢?就得写成下面这个样子:

final Reader inputString = new StringReader("www.howardliu.cn 看山");
final BufferedReader br = new BufferedReader(inputString);
// 其他一些逻辑
try (BufferedReader br1 = br) {System.out.println(br1.lines());
}

在 Java9 中,如果资源是final定义的或者等同于final变量,就不用声明新的变量名,可以直接在try-with-resources中使用:

final Reader inputString = new StringReader("www.howardliu.cn 看山");
final BufferedReader br = new BufferedReader(inputString);
// 其他一些逻辑
try (br) {System.out.println(br.lines());
}

改进钻石操作符 (Diamond Operator)

钻石操作符(也就是<>)是 Java7 引入的,可以简化泛型的书写,比如:

Map<String, List<String>> strsMap = new TreeMap<String, List<String>>();

右侧的TreeMap类型可以根据左侧的泛型定义推断出来,借助钻石操作符可以简化为:

Map<String, List<String>> strsMap = new TreeMap<>();

看山会简洁很多,<>的写法就是钻石操作符 (Diamond Operator)。

但是这种写法不适用于匿名内部类。比如有个抽象类:

abstract static class Consumer<T> {private T content;public Consumer(T content) {this.content = content;}abstract void accept();public T getContent() {return content;}
}

在 Java9 之前,想要实现匿名内部类,就需要写成:

final Consumer<Integer> intConsumer = new Consumer<Integer>(1) {@Overridevoid accept() {System.out.println(getContent());}
};
intConsumer.accept();final Consumer<? extends Number> numConsumer = new Consumer<Number>(BigDecimal.TEN) {@Overridevoid accept() {System.out.println(getContent());}
};
numConsumer.accept();final Consumer<?> objConsumer = new Consumer<Object>("看山") {@Overridevoid accept() {System.out.println(getContent());}
};
objConsumer.accept();

在 Java9 之后就可以使用钻石操作符了:

final Consumer<Integer> intConsumer = new Consumer<>(1) {@Overridevoid accept() {System.out.println(getContent());}
};
intConsumer.accept();final Consumer<? extends Number> numConsumer = new Consumer<>(BigDecimal.TEN) {@Overridevoid accept() {System.out.println(getContent());}
};
numConsumer.accept();final Consumer<?> objConsumer = new Consumer<>("看山") {@Overridevoid accept() {System.out.println(getContent());}
};
objConsumer.accept();

私有接口方法

如果说钻石操作符是代码的简洁可读,那接口的私有方法就是比较实用的一个扩展了。

在 Java8 之前,接口只能有常量和抽象方法,想要有具体的实现,就只能借助抽象类,但是 Java 是单继承,有很多场景会受到限制。

在 Java8 之后,接口中可以定义默认方法和静态方法,提供了很多扩展。但这些方法都是public方法,是完全对外暴露的。如果有一个方法,只想在接口中使用,不想将其暴露出来,就没有办法了。这个问题在 Java9 中得到了解决。我们可以使用private修饰,限制其作用域。

比如:

public interface Metric {// 常量String NAME = "METRIC";// 抽象方法void info();// 私有方法private void append(String tag, String info) {buildMetricInfo();System.out.println(NAME + "[" + tag + "]:" + info);clearMetricInfo();}// 默认方法default void appendGlobal(String message) {append("GLOBAL", message);}// 默认方法default void appendDetail(String message) {append("DETAIL", message);}// 私有静态方法private static void buildMetricInfo() {System.out.println("build base metric");}// 私有静态方法private static void clearMetricInfo() {System.out.println("clear base metric");}
}

JShell

JShell 就是 Java 语言提供的 REPL(Read Eval Print Loop,交互式的编程环境)环境。在 Python、Node 之类的语言,很早就带有这种环境,可以很方便的执行 Java 语句,快速验证一些语法、功能等。

$ jshell
|  欢迎使用 JShell -- 版本 13.0.9
|  要大致了解该版本,请键入:/help intro

我们可以直接使用/help查看命令

jshell> /help
|  键入 Java 语言表达式,语句或声明。
|  或者键入以下命令之一:
|  /list [<名称或 id>|-all|-start]
|   列出您键入的源
|  /edit <名称或 id>。很多的内容,鉴于篇幅,先隐藏

我们看下一些简单的操作:

jshell> "This is a test.".substring(5, 10);
$2 ==> "is a "jshell> 3+1
$3 ==> 4

也可以创建方法:

jshell> int mulitiTen(int i) { return i*10;}
|  已创建 方法 mulitiTen(int)jshell> mulitiTen(3)
$6 ==> 30

想要退出 JShell 直接输入:

jshell> /exit
|  再见

JCMD 新增子命令

jcmd是用于向本地 jvm 进程发送诊断命令,这个命令是从 JDK7 提供的命令行工具,常用于快速定位线上环境故障。

在 JDK9 之后,提供了一些新的子命令,查看 JVM 中加载的所有类及其继承结构的列表。比如:

$ jcmd 22922 VM.class_hierarchy -i -s java.net.Socket
22922:
java.lang.Object/null
|--java.net.Socket/null
|  implements java.io.Closeable/null (declared intf)
|  implements java.lang.AutoCloseable/null (inherited intf)
|  |--sun.nio.ch.SocketAdaptor/null
|  |  implements java.lang.AutoCloseable/null (inherited intf)
|  |  implements java.io.Closeable/null (inherited intf)

第一个参数是进程 ID,都是针对这个进程执行诊断。我们还可以使用set_vmflag参数在线修改 JVM 参数,这种操作无需重启 JVM 进程。

有时候还需要查看当前进程的虚拟机参数选项和当前值:jcmd 22922 VM.flags -all

多分辨率图像 API

在 Java9 中定义了多分辨率图像 API,我们可以很容易的操作和展示不同分辨率的图像了。java.awt.image.MultiResolutionImage将一组具有不同分辨率的图像封装到单个对象中。java.awt.Graphics类根据当前显示 DPI 度量和任何应用的转换从多分辨率图像中获取变量。

以下是多分辨率图像的主要操作方法:

  • Image getResolutionVariant(double destImageWidth, double destImageHeight):获取特定分辨率的图像变体-表示一张已知分辨率单位为 DPI 的特定尺寸大小的逻辑图像,并且这张图像是最佳的变体。
  • List<Image> getResolutionVariants():返回可读的分辨率的图像变体列表。

我们来看下应用:

final List<Image> images = List.of(ImageIO.read(new URL("https://static.howardliu.cn/about/kanshanshuo_2.png")),ImageIO.read(new URL("https://static.howardliu.cn/about/hellokanshan.png")),ImageIO.read(new URL("https://static.howardliu.cn/about/evil%20coder.jpg"))
);// 读取所有图片
final MultiResolutionImage multiResolutionImage = new BaseMultiResolutionImage(images.toArray(new Image[0]));// 获取图片的所有分辨率
final List<Image> variants = multiResolutionImage.getResolutionVariants();System.out.println("Total number of images: " + variants.size());for (Image img : variants) {System.out.println(img);
}// 根据不同尺寸获取对应的图像分辨率
Image variant1 = multiResolutionImage.getResolutionVariant(100, 100);
System.out.printf("\nImage for destination[%d,%d]: [%d,%d]",100, 100, variant1.getWidth(null), variant1.getHeight(null));Image variant2 = multiResolutionImage.getResolutionVariant(200, 200);
System.out.printf("\nImage for destination[%d,%d]: [%d,%d]",200, 200, variant2.getWidth(null), variant2.getHeight(null));Image variant3 = multiResolutionImage.getResolutionVariant(300, 300);
System.out.printf("\nImage for destination[%d,%d]: [%d,%d]",300, 300, variant3.getWidth(null), variant3.getHeight(null));Image variant4 = multiResolutionImage.getResolutionVariant(400, 400);
System.out.printf("\nImage for destination[%d,%d]: [%d,%d]",400, 400, variant4.getWidth(null), variant4.getHeight(null));Image variant5 = multiResolutionImage.getResolutionVariant(500, 500);
System.out.printf("\nImage for destination[%d,%d]: [%d,%d]",500, 500, variant5.getWidth(null), variant5.getHeight(null));

变量句柄(Variable Handles)

变量句柄(Variable Handles)的 API 主要是用来替代java.util.concurrent.atomic包和sun.misc.Unsafe类的部分功能,并且提供了一系列标准的内存屏障操作,用来更加细粒度的控制内存排序。一个变量句柄是一个变量(任何字段、数组元素、静态表里等)的类型引用,支持在不同访问模型下对这些类型变量的访问,包括简单的 read/write 访问,volatile 类型的 read/write 访问,和 CAS(compare-and-swap) 等。

这部分内容涉及反射、内联、并发等内容,后续会单独介绍,文章最终会发布在 从小工到专家的 Java 进阶之旅 中,敬请关注。

发布-订阅框架

在 Java9 中增加的java.util.concurrent.Flow支持响应式 API 的发布-订阅框架,他们提供在 JVM 上运行的许多异步系统之间的互操作性。我们可以借助SubmissionPublisher定制组件。

关于响应式 API 的内容可以先查看 http://www.reactive-streams.org/的内容,后续单独介绍,文章最终会发布在 从小工到专家的 Java 进阶之旅 中,敬请关注。怎么感觉给自己刨了这么多坑,得抓紧时间填坑了。

统一 JVM 日志记录

在这个版本中,为 JVM 的所有组件引入了一个通用的日志系统。它提供了日志记录的基础。这个功能是通过-Xlog启动参数指定,并且定义很多标签用来定义不同类型日志,比如:gc(垃圾收集)、compiler(编译)、threads(线程)等等。比如,我们定义debug等级的 gc 日志,日志存储在gc.log文件中:

java -Xlog:gc=debug:file=gc.log:none

因为参数比较多,我们可以通过java -Xlog:help查看具体定义参数。而且日志配置可以通过jcmd命令动态修改,比如,我们将日志输出文件修改为gc_other.log

jcmd ${PID} VM.log output=gc_other.log what=gc

新的 API

不可变集合

在 Java9 中增加的java.util.List.of()java.util.Set.of()java.util.Map.of()系列方法,可以一行代码创建不可变集合。在 Java9 之前,我们想要初始化一个有指定值的集合,需要执行一堆addput方法,或者依赖guava框架。

而且,这些集合对象是可变的,假设我们将值传入某个方法,我们就没有办法控制这些集合的值不会被修改。在 Java9 之后,我们可以借助ImmutableCollections中的定义实现初始化一个不可变的、有初始值的集合了。如果对这些对象进行修改(新增元素、删除元素),就会抛出UnsupportedOperationException异常。

这里不得不提的是,Java 开发者们也是考虑了性能,针对不同数量的集合,提供了不同的实现类:

  • List12Set12Map1专门用于少量(List 和 Set 是 2 个,对于 Map 是 1 对)元素数量的场景
  • ListNSetNMapN用于数据量多(List 和 Set 是超过 2 个,对于 Map 是多余 1 对)的场景

改进的 Optional 类

Java9 中为Optional添加了三个实用方法:streamifPresentOrElseor

stream是将Optional转为一个Stream,如果该Optional中包含值,那么就返回包含这个值的Stream,否则返回Stream.empty()。比如,我们有一个集合,需要过滤非空数据,在 Java9 之前,写法如下:

final List<Optional<String>> list = Arrays.asList(Optional.empty(),Optional.of("看山"),Optional.empty(),Optional.of("看山的小屋"));final List<String> filteredList = list.stream().flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty()).collect(Collectors.toList());

在 Java9 之后,我们可以借助stream方法:

final List<String> filteredListJava9 = list.stream().flatMap(Optional::stream).collect(Collectors.toList());

ifPresentOrElse:如果一个Optional包含值,则对其包含的值调用函数action,即action.accept(value),这与ifPresent方法一致;如果Optional不包含值,那会调用emptyAction,即emptyAction.run()。效果如下:

Optional<Integer> optional = Optional.of(1);
optional.ifPresentOrElse(x -> System.out.println("Value: " + x), () -> System.out.println("Not Present."));optional = Optional.empty();
optional.ifPresentOrElse(x -> System.out.println("Value: " + x), () -> System.out.println("Not Present."));// 输出结果为:
// 作者:看山
// 佚名

or:如果值存在,返回Optional指定的值,否则返回一个预设的值。效果如下:

Optional<String> optional1 = Optional.of("看山");
Supplier<Optional<String>> supplierString = () -> Optional.of("佚名");
optional1 = optional1.or(supplierString);
optional1.ifPresent(x -> System.out.println("作者:" + x));optional1 = Optional.empty();
optional1 = optional1.or(supplierString);
optional1.ifPresent(x -> System.out.println("作者:" + x));// 输出结果为:
// 作者:看山
// 作者:佚名

文末总结

本文介绍了 Java9 新增的特性,完整的特性清单可以从https://openjdk.java.net/projects/jdk9/查看。文中也给自己刨了几个坑,碍于篇幅,没有办法展开,所有这些需要展开的功能细述,都会在 Java8 到 Java17 的新特性系列完成后补充,博文会发布在 从小工到专家的 Java 进阶之旅 系列专栏中。

推荐阅读

  • 一文掌握 Java8 Stream 中 Collectors 的 24 个操作
  • 一文掌握 Java8 的 Optional 的 6 种操作
  • 使用 Lambda 表达式实现超强的排序功能
  • Java8 的时间库(1):介绍 Java8 中的时间类及常用 API
  • Java8 的时间库(2):Date 与 LocalDate 或 LocalDateTime 互相转换
  • Java8 的时间库(3):开始使用 Java8 中的时间类
  • Java8 的时间库(4):检查日期字符串是否合法
  • Java8 的新特性

你好,我是看山。游于码界,戏享人生。如果文章对您有帮助,请点赞、收藏、关注。我还整理了一些精品学习资料,关注公众号「看山的小屋」,回复“资料”即可获得。

个人主页:https://www.howardliu.cn
个人博文:Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java9 的新特性
CSDN 主页:https://kanshan.blog.csdn.net/
CSDN 博文:Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java9 的新特性

Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java9 的新特性相关推荐

  1. Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java11 的新特性

    文章目录 概述 增强 String repeat strip.stripLeading.stripTrailing isBlank lines 增强文件读写 增强集合的数组操作 增强函数 Predic ...

  2. Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java12 的新特性

    本文收录在 <从小工到专家的 Java 进阶之旅> 系列专栏中. 你好,我是看山. 从 2017 年开始,Java 版本更新策略从原来的每两年一个新版本,改为每六个月一个新版本,以快速验证 ...

  3. Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java10 的新特性

    你好,我是看山. 本文收录在 <从小工到专家的 Java 进阶之旅> 系列专栏中. 从 2017 年开始,Java 版本更新策略从原来的每两年一个新版本,改为每六个月一个新版本,以快速验证 ...

  4. Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java13 的新特性

    你好,我是看山. 本文收录在 <从小工到专家的 Java 进阶之旅> 系列专栏中. 从 2017 年开始,Java 版本更新策略从原来的每两年一个新版本,改为每六个月一个新版本,以快速验证 ...

  5. JDK 每半年就会更新一次新特性,再不掌握就要落伍了:JDK8 的新特性

    该图片由Alexandr Podvalny在Pixabay上发布 你好,我是看山. 本文收录在 <Java 进阶> 系列专栏中. 从 2017 年开始,JDK 版本更新策略从原来的每两年一 ...

  6. Java代码优化(长期更新)

    前言 2016年3月修改,结合自己的工作和平时学习的体验重新谈一下为什么要进行代码优化.在修改之前,我的说法是这样的: 就像鲸鱼吃虾米一样,也许吃一个两个虾米对于鲸鱼来说作用不大,但是吃的虾米多了,鲸 ...

  7. 漫画:Java如何实现热更新?

    Arthas(阿尔萨斯)是 Alibaba 开源的一款 Java 诊断工具,使用它我们可以监控和排查 Java 程序,然而它还提供了非常实用的 Java 热更新功能. 所谓的 Java 热更新是指在不 ...

  8. java 替换 rn_RN热更新之Android篇

    前言 这篇来研究一下RN的热更新,之前看资料见到过两个现成的方案: 1.reactnative中文网的pushy 不过看了文档就觉得没劲,不如自己来实现,况且之前已经有点门路了. 原理 关于热更新的原 ...

  9. ApacheCN Java 译文集 20210921 更新

    ApacheCN Java 译文集 20210921 更新 新增了五个教程: Java 设计模式最佳实践 零.前言 一.从面向对象到函数式编程 二.创建型模式 三.行为模式 四.结构模式 五.函数式模 ...

最新文章

  1. AbstractQueuedSynchronizer 原理分析 - 独占/共享模式
  2. Angular 如何根据一个 class 的定义和数据,动态创建一个该类的实例
  3. java的重写、重载、覆盖的差别
  4. matlab2018b中svm无法运行,关于matlab2018a版本错误使用 svmclassify 分类器
  5. 安卓手机XPosed框架安装(详细版本)
  6. linux卸载内核网卡驱动,Linux下网卡驱动的安装
  7. 基于Python实现的死链接自动化检测工具
  8. 微信公众号两种匹配模式(全匹配和半匹配)的区别。
  9. 2021最新 从零开始搭建terraria(泰拉瑞亚)云服务器
  10. 电气工程师软件笔试题
  11. 【深度学习基础1】深度学习发展历史
  12. 如何解决模具折弯尺寸不稳定?
  13. uniapp 设置桌面角标
  14. Vue前端模板框架--vue-admin-template
  15. 使用GHOST对Windows操作系统进行备份和还原
  16. Cocos Creator入门实战:桌球小游戏
  17. 以太坊区块链浏览器(一)拿来就用主义
  18. 涂鸦标准模组MCU SDK开发流程
  19. CF1717D Madoka and The Corruption Scheme
  20. SetForegroundWindow、SetActiveWindow、SetFocus 如何将一个某个窗口提到最顶层

热门文章

  1. SpringBoot中@Lazy的使用
  2. 市场调研-全球与中国VR播放器市场现状及未来发展趋势
  3. java常用类库以及集合
  4. 独家首发java品优购项目课程,20天课程,430个知识点!视频+资料全部免费领!
  5. Java开发常用软件下载地址合集
  6. msn无法登陆...
  7. 斐波那契数列和青蛙跳台阶问题
  8. CTF学习笔记22:iwebsec-文件上传漏洞-07-条件竞争文件上传
  9. 《游戏设计艺术(第二版)》第十三章个人学习
  10. 技术分享 | 浅谈 MySQL 的临时表和临时文件