概述
JDK 12 于 2019 年 3 月 19 日正式发布。。
JEP(Java Enhancement Proposal)Java增强提案
变动说明
官网:
更多参考:
docs.oracle.com/en/java/jav… 更多版本:docs.oracle.com/en/java/jav…
重要变更和信息
作为“功能性版本”,JDK 12 总共包含 8 个新的 JEP ,分别为:
- JEP 189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental):新增一个名为 Shenandoah 的垃圾回收器,它通过在 Java 线程运行的同时进行疏散 (evacuation) 工作来减少停顿时间。
- JEP 230: Microbenchmark Suite:新增一套微基准测试,使开发者能够基于现有的 Java Microbenchmark Harness(JMH)轻松测试 JDK 的性能,并创建新的基准测试。
- JEP 325: Switch Expressions (Preview) :对 switch语句进行扩展,使其可以用作语句或表达式,简化日常代码,这是一个预览特性。
- JEP 334: JVM Constants API :引入一个 API 来对关键类文件 (key class-file)和运行时工件的名义描述(nominal descriptions)进行建模,特别是那些可从常量池加载的常量。
- JEP 340: One AArch64 Port, Not Two :删除与 arm64 端口相关的所有源码,保留 32 位 ARM 移植和64 位 aarch64 移植。
- JEP 341: Default CDS Archives :默认生成类数据共享(CDS)存档。
- JEP 344: Abortable Mixed Collections for G1 :当 G1垃圾回收器的回收超过暂停目标,则能中止垃圾回收过程。
- JEP 346: Promptly Return Unused Committed Memory from G1 :改进 G1垃圾回收器,以便在空闲时自动将 Java 堆内存返回给操作系统。
跟开发相关的特性主要是:JEP 325。
预览特性
预览特性是一种新特性,其设计、规范和实现都是完整的,但不是永久的,这意味着该特性可能以不同的形式存在,或者在未来的JDK版本中根本不存在。
在主流JDK版本中引入一个特性作为预览特性,可以让尽可能多的开发人员试用该特性并提供反馈。开发人员的反馈有助于确定该特性是否存在任何设计错误,包括硬技术错误(如类型系统中的缺陷)、软可用性问题(如与旧特性的意外交互)或糟糕的架构选择(如无法确定未来特性的方向)。通过这些反馈,评估该特性的优势和劣势,以确定该特性是否在Java SE平台中具有长期作用,如果是,是否需要改进。因此,该特征可能被授予最终和永久的地位(有或没有改进),或经历进一步的预览期(有或无改进),或者被删除。
每个预览特性都由JDK增强方案(JEP)描述,该方案定义了其范围并绘制了其设计草图。例如,JEP 325描述了预览特征“Switch 表达式”。有关预览特性的角色和生命周期的背景信息,请参阅JEP 12。
要在程序中使用预览语言特性,必须在编译器和运行时系统中显式启用这些特性。如果没有,您将收到一条错误消息,提示你的代码正在使用预览特性,并且默认情况下禁用了预览特性。
启用预览特性,需要javac与--enable预览命令行选项以及--release jdkVersion或-source jdkVersion命令行选项一起使用。如下,表示使用JDK12编译MyApp.java这个java文件,并启用预览特性。
javac --enable-preview --release 12 MyApp.java
下载地址
您可以从这个链接下载生产就绪的OpenJDK版本。文件为压缩包,解压并设置环境变量就可以使用。
当然你也可以从这个链接下载Oracle JDK版本(但是需要注意商用限制),更多版本下载。
Java12新特性总结
总体而言,Java12还有一些预览特性和JVM增强特性,并没有太多的亮点。
1、JEP 325:Switch 表达式(预览特性)
这是一个预览特性。
Java语言增强了switch语句,使其既可以用作语句也可以用作表达式。使用switch作为表达式通常会导致代码更加简洁和易读。语句和表达式形式都可以使用传统的case … :标签(冒号,存在下落(Fall-through)行为)或简化case … ->标签(箭头,不存在下落(Fall-through)行为)。此外,两种形式都可以在一种情况下打开多个常量。
Switch 进化:
Switch 进化:
| java版本 | JEP | 特性 |
|---|---|---|
| Java 5 | 首次引入,仅支持整型数据类型(如 byte, short, char, 和 int)及枚举类型 | |
| Java 7 | 支持 String 类型 | |
| Java 12 | JEP 325 | 预览特性:支持Switch表达式(箭头函数) |
在java12之前版本的Switch存在下面的问题:
- 下落(Fall-through) 行为:不写
break语句的情况下,Switch 语句会从一个 case "穿透" 到下一个 case,忽略了这个会导致不可饶恕的错误。 - 代码冗余:每个 case,我们都需要重复类似的代码结构,增加了代码的冗余和维护难度。
下面通过代码感受一下新旧Switch之间的差异:
public static String getTypeOfDay1(String name) {
String desp;
switch (name) {
case "稻":
desp = "dào,俗称水稻、大米";
break; //注意每个 case 之后需要 break 语句,否则会发生穿透。即继续执行下一个,可以去掉break试试。
case "黍":
desp = "shǔ,俗称黄米";
break;
case "稷":
desp = "jì,又称粟,俗称小米";
break;
case "麦":
desp = "mài,俗称小麦";
break;
case "菽":
case "豆":
desp = "shū,俗称大豆";
break;
default:
throw new IllegalStateException("不是五谷之一: " + name);
}
return desp;
}
public static String getTypeOfDay2(String name) {
return switch (name) {
case "稻" -> "俗称水稻、大米";
case "黍" -> "shǔ,俗称黄米";
case "稷" -> "jì,又称粟,俗称小米";
case "麦" -> "俗称小麦";
case "菽", "豆" -> "shū,俗称大豆";
default -> throw new IllegalStateException("不是五谷之一: " + name);
};
}
@Test(priority = 0) // 不指定顺序时默认按字母顺序执行
public void test() {
String name = "稷";
System.out.printf("%s:%s%n", name, getTypeOfDay1(name));
System.out.printf("%s:%s%n", name, getTypeOfDay2(name));
}
执行结果:
稷:jì,又称粟,俗称小米
稷:jì,又称粟,俗称小米
从代码上可以看出来,新的API简洁了很多。从这里可以看出他们之间的差异如下:
- 返回值:旧的 Switch 无法返回值,需要在Switch外定义变量用于接收Switch处理的值,新的 Switch 可以作为表达式使用,可以返回值。
->代替::老的 Switch 需要在每个案例后面使用 break ,否则会发生“穿透”,而新的不需要,它会自动终止。- 多值匹配:旧的 Switch 无法在一个案例标签中匹配多个值,而新的 Switch 表达式允许一个
case匹配多个值,用","分割即可。
2、新增String API
为了进一步增加对 String 的处理能力,Java 12 对 String 进一步增强,引入几个方法:
| 方法名 | 描述 |
|---|---|
indent(int n) | 根据参数n对字符串进行缩进 |
transform() | 将字符串转换为 R 形式的结果。 |
describeConstable() | 该方法用于支持新的常量 API,它返回一个 Optional,描述字符串的常量描述。 |
resolveConstantDesc() | 则与 describeConstable() 相关,用于解析常量描述 |
indent()
public String indent(int n)
indent(int n) 会根据参数n对字符串进行缩进。规则如下:
- 当
n>0时,会在字符串的每一行开头插入n个空格,字符串整体右移。 - 当
n<0时,会在字符串的每一行开头删除n个空格,如果实际的空格数小于n,删除该行所有空格,但是并不会换行。
@Test
public void test() {
String text = " 金 \n 木 \n 水 \n 火 \n 土";
System.out.println("缩进前");
System.out.println(text);
System.out.println("往右缩进2个字符");
String indent2 = text.indent(2);
System.out.println(indent2);
System.out.println("往左缩进3个字符,实际不够只有2个空格");
String indent3 = text.indent(-3);
System.out.println(indent3);
}
执行结果:
缩进前
金
木
水
火
土
往右缩进2个字符
金
木
水
火
土
往左缩进3个字符,实际不够只有2个空格
金
木
水
火
土
注意:这个方法执行后最后会多加一个换行。
transform()
public <R> R transform(Function<? super String, ? extends R> f)
该方法接受一个函数作为参数,对字符串进行转换,并返回转换的结果。该方法非常强大,强大之处在于它的泛型和功能性特点,它允许我们对字符串执行任意类型的操作并返回所需的类型。例如:
@Test
public void test1() {
String txt = "金木水火土 ";
String s = txt.transform(str -> str.repeat(2));
System.out.println(s);
String s1 = txt.transform(str -> "五行:" + str.indent(2));
System.out.println(s1);
}
执行结果:
金木水火土 金木水火土
五行: 金木水火土
该方法在对字符串执行复杂转换的场景中特别有用。
describeConstable()和resolveConstantDesc()
describeConstable()和resolveConstantDesc()比较底层,主要主要是为了支持 Java 中的常量描述功能,在日常的 Java 应用程序开发中可能不是经常使用,这里就不介绍了。
3、新增Files API
Java 12 在Files工具类中增加了一个新的静态方法Files.mismatch(Path,Path),用来找两个文件内容(byte)不一样的地方,返回两个文件内容中第一个不匹配字节的位置,如果完全相同,则返回-1L 。
@Test
public void test() throws IOException {
// 文件对比
Path p1 = Files.createTempFile("file1", "txt");
Path p2 = Files.createTempFile("file2", "txt");
Files.writeString(p1, "金木水火土");
Files.writeString(p2, "金木水火土");
// -1L 二者内容相同。该方法用于比较两个文件的内容,它返回两个文件内容第一次不匹配的位置索引。如果文件完全相同,则返回 -1。
long mismatch = Files.mismatch(p1, p2);
System.out.println(mismatch);
}
该方法和另外一个方法Files.isSameFile(Path, Path)的比较:
| 文件关系 | isSameFile(Path,Path) | mismatch(Path,Path) |
|---|---|---|
| 同一文件 | true | -1(等同于true) |
| 复制文件 | false | -1(等同于true) |
| 不同文件 | false | 看内容,内容相同-1,内容不同返回第一次不匹配的位置索引 |
| 硬链接 | true | -1(等同于true) |
| 软连接 | true | -1(等同于true) |
isSameFile(Path, Path)比较的是路径mismatch(Path, Path)比较的是内容
该方法可用于确认两个文件内容是否完全相同。
4、新增 NumberFormat API
core-libs/java.text
NumberFormat增加了新的基于区域对以紧凑形式格式化数字的支持。紧凑数字格式是指以简短或人类可读的形式表示数字。例如,在en_US区域设置中,1000可以格式化为1K,1000000可以格式为1M,具体取决于NumberFormat指定的样式。压缩数字格式由LDML的压缩数字格式规范定义。
要获得一个实例,请使用NumberFormat提供的工厂方法之一进行紧凑的数字格式设置。示例如下:
@Test
public void test() throws IOException {
// 中国格式
NumberFormat chnFormat = NumberFormat.getCompactNumberInstance(Locale.CHINA, NumberFormat.Style.SHORT);
chnFormat.setMaximumFractionDigits(2);// 1.23亿
String cformat = chnFormat.format(123456789);
System.out.println(cformat);
// 美国格式
NumberFormat usFormat = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
usFormat.setMaximumFractionDigits(2);// 123.46M
String uformat = usFormat.format(123456789);
System.out.println(uformat);
}
5、新增 Collectors API
对Stream流的聚合操作Collector进一步增强,增加了teeing操作来实现一些复杂的聚合操作。它接受两个收集器(Collector)和一个二元运算函数(BiFunction),可以并行地将数据发送到两个不同的收集器,并在最后将这两个收集器的结果合并。
public static <T, R1, R2, R>
Collector<T, ?, R> teeing(Collector<? super T, ?, R1> downstream1,
Collector<? super T, ?, R2> downstream2,
BiFunction<? super R1, ? super R2, R> merger)
downstream1和downstream2:两个不同的Collector实例。merger:一个BiFunction,用于合并两个Collector的结果。
teeing() 适合于那些需要对同一个数据集进行多次处理的情景。
使用场景举例:同时计算一个数字列表的平均值和总和,或者找出一个员工列表中工资最高和最低的员工。
比如有一个整数列,我们需要计算出这个列的最大值和平均值。
举个例子,我如果想统计一个数组的平均数在总和的占比,首先要计算平均数,然后再计算总和,然后再相除,这样需要三个步骤。
@Test
public void test() throws IOException {
List<Double> renkou = List.of(54.058, 62.113, 67.665, 95.992, 108.997);
// 潍坊最近5年出生人口(万)平均值为77.765(万)
Double average = renkou.stream().collect(Collectors.averagingDouble(i -> i));
// 潍坊最近5年出生人口(万)总数为 388.825(万)
Double total = renkou.stream().collect(Collectors.summingDouble(i -> i));
System.out.printf("平均值:%s 总数:%s%n", average, total);
// 因为存在双精度问题,临时使用乘以10000,最后结果再除以10000解决
Map<String, Double> meanPercentage = renkou.stream().collect(Collectors.teeing(Collectors.averagingDouble(i -> i * 10000),
Collectors.summingDouble(i -> i * 10000), (avg, totals) -> Map.of("averag", avg / 10000, "total", totals / 10000)));
System.out.println(meanPercentage);
}
运行结果:注意这里面存在浮点型数据计算不准确的问题,这里我们先不讨论这个问题。
平均值:77.76500000000001 总数:388.82500000000005
{averag=77.765, total=388.825}
显然新的代码更简洁,但是它可能会带来一些性能开销,尤其是在处理大数据集时。所以使用时,最好做好评估。
6、JEP 189:Shenandoah 一种低暂停时间垃圾回收器(实验特性)
添加一个名为Shenandoah的新垃圾收集(GC)算法,该算法通过在运行Java线程的同时执行疏散工作来减少GC暂停时间。Shenandoah的暂停时间与堆大小无关,这意味着无论堆是200 MB还是200 GB,您都将有相同的一致暂停时间。
7、JEP 230:微基准测试套件(JMH)的支持
在JDK源代码中添加一套基本的微基准,使开发人员可以轻松地运行现有的微基准并创建新的微基准。
8、JEP 334:JVM 常量 API
引入API对关键类和运行时工件的名义描述进行建模,特别是可从常量池加载的常量。
新的包java.lang.invoke.constant引入了一个API来建模类文件和运行时工件的名义描述,特别是可从常量池加载的常量。它通过定义一系列基于值的符号引用(JVMS 5.1)类型来实现这一点,这些类型能够描述每种可加载常量。符号引用以纯粹的名义形式描述可加载常量,与类加载或可访问性上下文无关。一些类可以充当它们自己的符号引用(例如String);为可链接常量添加了一系列符号引用类型(ClassDesc、MethodTypeDesc、MethodHandleDesc和DynamicConstantDesc),这些类型包含描述这些常量的标称信息。
9、JEP 340:移除多余ARM64实现
删除与arm64端口相关的所有源,同时保留32位ARM端口和64位aarch64端口。
在 Java 12 之前,Java 有两个 AArch64 端口:一个用于 OpenJDK,另一个用于 Oracle JDK。这造成了维护上的重复和效率问题。
Java 12 合并两个 AArch64 端口,同时统一他们的代码库,这就意味着未来所有的 AArch64 特定代码将在同一个代码库中维护,从而简化了开发和维护工作。
10、JEP 341:默认CDS归档
增强JDK的构建过程,在64位平台上使用默认的类列表生成类数据共享(CDS)档案。
11、JEP 344:G1的可中断 mixed GC
Java 12 之前,G1 垃圾收集器在执行混合收集(即同时回收年轻代和老年代)时,有时会导致较长的停顿时间,这对那些低延迟的应用来说是一个不可接受的问题。
Java 12 引入了一种机制,允许 G1 垃圾收集器在执行混合收集时,在超过预设的停顿时间阈值时提前中断收集过程。
12、JEP 346:从G1立即返回未使用的已提交内存
增强G1垃圾收集器,使其在空闲时自动将Java堆内存返回到操作系统。
13、移除的APIs、工具、容器
参考:
Oracle JDK和OpenJDK之间的差异
尽管官方已经声明了让OpenJDK和Oracle JDK二进制文件尽可能接近的目标,但至少对于JDK 12来说,这两个选项之间仍然存在一些差异。
目前的差异是:
Oracle JDK提供了安装程序(msi、rpm、deb等),它们不仅将JDK二进制文件放置在系统中,还包含更新规则,在某些情况下还可以处理一些常见的配置,如设置常见的环境变量(如Windows中的JAVA_HOME)和建立文件关联(如使用JAVA启动.jar文件)。OpenJDK仅作为压缩档案(tar.gz或.zip)提供。- 在版本号为
9和10下,javac —release的行为会有所不同。Oracle JDK的二进制文件中包含了一些没有加入到OpenJDK二进制文件中的 API,比如javafx、资源管理以及(在 JDK 11 变更之前)JFR APIs。 Usage Logging仅在Oracle JDK中可用。- 如果使用了
-XX:+UnlockCommercialFeatures标志,OpenJDK将(继续)抛出一个错误并停止。Oracle JDK不再需要该标志,并且将打印警告,但如果使用,则继续执行。 Oracle JDK要求使用Java加密扩展(JCE(Java Cryptography Extension ))代码签名证书对第三方加密提供程序进行签名。OpenJDK继续允许使用未签名的第三方加密提供程序。java -version命令输出结果不同。Oracle JDK将输出java并包含LTS。Oracle生成的OpenJDK将显示OpenJDK,不包括Oracle特定的LTS标识符。Oracle JDK将在OTN许可证下发布。任何许可证文件都需要指向OTN。OpenJDK将在GPLv2wCP下发布,并将包括GPL许可证。Oracle JDK将在FreeType许可证下分发FreeType,而OpenJDK则将在GPLv2下分发。因此,\legal\java.desktop\freetype.md的内容将有所不同。Oracle JDK有Java cup和steam图标,而OpenJDK有Duke图标。Oracle JDK源代码包括ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.的说明,OpenJDK源代码包括GPL许可条款。