一帆磨砺

生活所迫,一叶孤舟

0%

Java版本特性-JDK9

引用参考文档链接

  1. Oracle Java9官方文档
  2. JDK 9 Release Notes
  3. Module System Quick-Start Guide
  4. JDK9迁移指南
  5. Milling Project Coin
  6. jlink: The Java Linker
  7. The Java Shell

写在开头

  1. 本文仅涉及部分JDK9变更,完整可查看官方文档Standard Edition What’s New in Oracle JDK 9
  2. 除了模块化,其他改动对日常开发影响不多,可以着重关注模块化和接口私有函数
  3. 在后续的学习中发现,在模块化后,类似于ServiceLoader.load的类加载的方法都有变动,大多数都和模块之间的类可视化有关,如果是从低版本迁移,需要额外注意

模块化系统

案例

ModularDemo

因目前Oracle开放的JDK下载版本仅有8/11/17/18,因此该项目通过JDK18编译JDK9(Project language level设置为9)模拟

工程结构

Modular

总结

  1. commonservice是在 Maven 层面上的子模块,JDK9的模块化主要通过类module-info.java
  2. 每个工程(模块)仅可有一个module-info.java
  3. 对外开放的最小粒度是package,无法以类作为最小单位,如果有类不愿对外开放,建议迁移到单独的包中
  4. 打包后,虽然common没有开放intern包,但是最终common.jar中还是会有intern中的类
  5. 如果类不惜那个对外开放,但是其中部分功能还需要对外使用,可以单独在对外开放的包中新建一个类,作为转发调用
  6. 从初步的使用感觉,目前仅在权限控制方面有明显的作用
  7. maven-compiler-plugin仅在3.8.0+才支持module(可查看ModularDemo/pom.xml)

jlink

可以自定义Java程序运行时的JRE环境

命令使用

1
2
3
4
5
6
7
8
jlink --module-path <modulepath> --add-modules <modules> --limit-modules <modules> --output <path>
module-path: 指的是你自定义的JRE环境所有需要的module所处的路径
add-modules: 在 module-path 中,你需要那些module
limit-modules: 根据module名称限制搜寻范围
output: 自定义JRE的输出路径

jlink --output service/src/main/resources/jre --module-path "service/src/main/resources" --add-modules common
我将入门案例中的common的jar包拷贝到service/src/main/resources中,因此 module-path 就是 "service/src/main/resources",而因 service 仅需要 common 一个模块,因此我只添加了 common

优缺点

  1. 自定义的JRE环境没有无关的jar包,因此启动内存消耗会少
    1
    2
    3
    System.out.println("totalMemory:" + Runtime.getRuntime().totalMemory()/ (1024 * 1024));
    System.out.println("freeMemory:" + Runtime.getRuntime().freeMemory()/ (1024 * 1024));
    System.out.println("maxMemory:" + Runtime.getRuntime().maxMemory()/ (1024 * 1024));
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    (base) MacBook-Pro ModularDemo % ./service/src/main/resources/jre/bin/java -jar service/target/service-1.0-SNAPSHOT.jar
    hello
    this is private class.
    1
    2
    3
    false
    totalMemory:512
    freeMemory:510
    maxMemory:8192
    (base) MacBook-Pro ModularDemo % java -jar service/target/service-1.0-SNAPSHOT.jar
    hello
    this is private class.
    1
    2
    3
    false
    totalMemory:520
    freeMemory:514
    maxMemory:8192
  2. 可以根据应用自定义不同的JRE,而不用将所有 module 对全部应用开放

总结

  1. 初步感受,该功能在嵌入式领域可以发挥非常重要的作用,在服务器端程序,作用可能没有那么明显
  2. 最好可以搭配The Modular JDKModular Source CodeModular Run-Time Images一起使用,效果更佳(此处埋坑,以后有时间再回头来研究这三个)

正则^调整作用范围

将作用到整个表达式,而不是第一个group

案例

1
2
3
4
5
6
7
Pattern compile = Pattern.compile("[^a-b[c-d]e-f]");
Matcher a = compile.matcher("a");
Matcher c = compile.matcher("c");
// JDK8 false JDK9 false
System.out.println(a.matches());
// JDK8 true JDK9 false
System.out.println(c.matches());

try-with-resources

可以在try外部声明变量,将变量名放在try中即可,但是该变量受final or effectively final规则限制,即在初始化完成后不可再次进行赋值操作;

1
2
3
4
5
6
7
8
InputStream in = Files.newInputStream(Paths.get("test.txt"));
try (in) {
// JDK8中该用法会有以下异常提示
// Resource references are not supported at language level '8'
...
} catch (IOException e) {
...
}

匿名类的泛型推断

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface InferredType <T>{
public void test(T t);
}
...
// JDK8 中会有如下异常提示
// Class 'Anonymous class derived from InferredType' must either be declared abstract or implement abstract method 'test(T)' in 'InferredType'
InferredType<String> inferredType = new InferredType<>() {
@Override
public void test(String o) {

}
};

允许接口定义private方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface PrivateFunc {

void func1();

default void func2() {
func3();
// JDK8+
System.out.println("默认实现");
}

private void func3() {
func4();
System.out.println("私有默认实现");
}

private static void func4() {
System.out.println("静态私有实现");
}
}
  1. 可以起到优化重复代码的帮助

_不可作为变量名

1
2
3
// As of Java 9, '_' is a keyword, and may not be used as an identifier
// JDK8 是可以正常使用的,影响不大,几乎很少有场景会单独使用 _ 作为变量名
String _ = new String();

@SafeVarargs

1
2
3
4
5
6
7
8
@SafeVarargs
// 不用该注解,仅会导致编译器提示,不会异常
// 此注解用于泛型可变入参函数,JDK9是拓展至可声明private函数
// Possible heap pollution from parameterized vararg type
private static <T> void saveVarargs(T... params) {
Arrays.stream(params).forEach(System.out::println);
}

Class文件版本号变动至53

编译旧版本范围变动

The javac command no longer supports -source or-target values for releases before 6/1.6. However, older class files are still readable by javac. Source code for an older release can be ported to a newer source level. To generate class files usable by releases older than JDK 6, a javac from a JDK 6, 7, or 8 release family can be used.
JEP 182 documents the policy for retiring old -source and -target options.

JDK9在使用javac编译java文件时,指定java的版本仅支持*1.6+*,不再支持编译到1.5以及更早期的版本

JShell

引用官方原文

Immediate feedback is important when learning a programming language and its APIs. The number one reason schools cite for moving away from Java as a teaching language is that other languages have a “REPL” and have far lower bars to an initial “Hello, world!” program. A Read-Eval-Print Loop (REPL) is an interactive programming tool which loops, continually reading user input, evaluating the input, and printing the value of the input or a description of the state change the input caused. Scala, Ruby, JavaScript, Haskell, Clojure, and Python all have REPLs and all allow small initial programs. JShell adds REPL functionality to the Java platform.
Exploration of coding options is also important for developers prototyping code or investigating a new API. Interactive evaluation is vastly more efficient in this regard than edit/compile/execute and System.out.println.
Without the ceremony of class Foo { public static void main(String[] args) { … } }, learning and exploration is streamlined.

大致含义是降低Java学习的入门门槛,可以更方便的测试API
吐槽:

  1. 对于习惯了ide的代码提示和代码模板(code templates)来说,突然使用jshell会有些难受

欢迎关注我的其它发布渠道