概述
Java 11 在 2018 年 9 月 25 日正式发布,与 Java 9 和 Java 10 这两个被称为”功能性的版本”不同,Java 11 提供长期支持服务(LTS, Long-Term-Support),将作为从发布日期开始的 Java 平台的默认支持版本,并且会提供技术支持直至 2023 年 9 月,对应的补丁和安全警告等支持将持续至 2026 年。
Java 11 是继 Java 8 之后的第二个 LTS 版本。自Java 11起,Oracle JDK 将不再免费用于商业用途。
您可以在开发阶段使用它,但若想将其用于商业环境,您需要购买许可证。 如果不这样做,可能会面临来自Oracle的法律诉讼。
Java 10 是最后一个可以免费使用的 Oracle JDK。
Oracle 从 2019 年 1 月起停止 Java 8 支持。您需要为更多支持付费。您可以继续使用Java 8,但是不会再收到任何补丁或安全更新。
从Java 11开始,Oracle 将不再为任何单一 Java 版本提供免费的长期支持 (LTS)。
尽管Oracle JDK不再免费,但您始终可以从 Oracle 或其他提供商(例如 AdoptOpenJDK、Azul、IBM、Red Hat 等)下载 Open JDK 版本。在我看来,除非您正在寻找有兴趣的企业级使用 支付支持费用,您可以使用 OpenJDK 并在必要时对其进行升级。
商用收费
Java的商用收费始于Java 8 ,Oracle JDK宣布从2019年1月份开始对Java SE 8之后的版本开始进行商用收费,确切的说是8u201/202之后的版本。如果你用 Java 开发的功能如果是用作商业用途的,如果还不想花钱购买的话,能免费使用的最新版本是8u201/202。当然如果是个人客户端或者个人开发者可以免费试用 Oracle JDK所有的版本。另外Oracle官方其实还提供了一个完全免费开源的JDK版本—— Open JDK,这是免费使用的。
决定是否收费首先得看JDK使用的是什么协议?
- BCL协议:即Oracle Binary Code License Agreement,协议规定你可以使用JDK,但是不能进行修改。私用和商用都可以,但是JDK中的某些商业特性,是需要付费才可以使用的。
- OTN协议:即Oracle Technology Network License Agreement,目前新发布的JDK用的都是这个协议,可以私用,商用需要付费。
版本说明
- Java 8以下版本,仍可免费使用。
- Java 8 (LTS) 部分免费,8u201/202及之前的版本是免费的,之后的商用收费。
- Java 9 是免费的,过渡版本且不再更新不建议使用。
- Java 10 是免费的,过渡版本且不再更新不建议使用。
- Java 11 (LTS) 开始Oracle JDK商用收费,同时提供免费版的Open JDK,下载地址。
补充说明
-
从2019年4月开始,Oracle JDK 8 更新将具有商业使用限制。
-
Java 10.0.2(2018年7月),Java 8u201/202(2019年1月)是 Oracle 发布的最后的免费的Oracle JDK。
-
从 Java 11 开始,每6个月发布一个新的版本,即 11 → 12 → 13 → 14。
-
从 Java 11 开始,每3个月更新一个补丁版本,即 11.0.1 → 11.0.2。
-
从 Java 11(2018年9月,LTS)开始,Oracle将提供基于GPLv2 + CPE协议的 OpenJDK,Oracle JDK构建和OpenJDK构建将基本相同。
-
免费的JDK有 OpenJDK 、 AdoptOpenJDK 、 Amazon Corretto 、 Azul Zulu 、 BellSoft 、 IBM 、 jClarity 、 Red Hat 、 SAP 、 阿里巴巴 Dragonwell等。
JEP(Java Enhancement Proposal)Java增强提案
变动说明
官网:
更多参考:
docs.oracle.com/en/java/jav… 更多版本:docs.oracle.com/en/java/jav…
重要变更和信息
以下是中的一些重要更改以及有关此版本的信息。
- Applets和Web Start在JDK 9中已被弃用,在JDK 11中已被删除。
- 删除了浏览器集成。
- 可用于Windows和macOS上JRE安装的自动更新已不可用。
- 在Windows和macOS中,在以前的版本中安装JDK可以选择安装JRE。在JDK 11中,这不再是一个选项。
- 在此版本中,不再提供JRE或服务器JRE。只提供JDK。用户可以使用jlink创建更小的自定义运行时。
- JDK中不再包含JavaFX。它现在可以从openjfx.io单独下载。
- JDK 7、8、9和10中提供的Java任务控制不再包含在Oracle JDK中。现在是单独下载。
- 以前的版本被翻译成英语、日语和简体中文,以及法语、德语、意大利语、韩语、葡萄牙语(巴西)、西班牙语和瑞典语。但是,在JDK11及更高版本中,不再提供法语、德语、意大利语、韩语、葡萄牙语(巴西)、西班牙语和瑞典语翻译。
- 更新后的Windows打包格式已从tar.gz更改为.zip,这在Windows操作系统中更为常见。
- macOS的更新包格式已从.app更改为.dmg,这更符合macOS的标准。
JDK 11 包含 17 个 新特性 ,分别为:
- JEP 181: Nest-Based Access Control
- JEP 309: Dynamic Class-File Constants
- JEP 315: Improve Aarch64 Intrinsics
- JEP 318: Epsilon: A No-Op Garbage Collector
- JEP 320: Remove the Java EE and CORBA Modules
- JEP 321: HTTP Client (Standard) HTTP Client标准化
- JEP 323: Local-Variable Syntax for Lambda Parameters 局部变量类型推导的升级
- JEP 324: Key Agreement with Curve25519 and Curve448
- JEP 327: Unicode 10
- JEP 328: Flight Recorder
- JEP 329: ChaCha20 and Poly1305 Cryptographic Algorithms
- JEP 330: Launch Single-File Source-Code Programs 运行单文件源码程序
- JEP 331: Low-Overhead Heap Profiling
- JEP 332: Transport Layer Security (TLS) 1.3
- JEP 333: ZGC: A Scalable Low-Latency Garbage Collector (Experimental)
- JEP 335: Deprecate the Nashorn JavaScript Engine
- JEP 336: Deprecate the Pack200 Tools and API
而其中与开发过程中直接相关的特性包括:JEP 323(局部变量类型推导的升级)、JEP 330(运行单文件源码程序)、JEP 321(HTTP Client标准化)等。
下载地址
您可以从这个链接下载生产就绪的OpenJDK版本。文件为压缩包,解压并设置环境变量就可以使用。
当然你也可以从这个链接下载Oracle JDK版本(但是需要注意商用限制),更多版本下载。
Java11新特性总结
1、JEP 323:局部变量类型推导的升级
switch的功能进化
| java版本 | 特性类型 | JEP | 特性 |
|---|---|---|---|
| Java 10 | 正式特性 | JEP 286 | 引入局部变量类型推导 |
| Java 11 | 正式特性 | JEP 323 | 增强 |
Java 10 引入了 局部变量类型推导这一关键特性。类型推导允许使用关键字 var 作为局部变量的类型而不是实际类型,编译器根据分配给变量的值推导出类型。这一改进简化了代码编写、节省了开发者的工作时间,因为不再需要显式声明局部变量的类型,而是使用关键字 var替代。
在 Java 10 中,var声明局部变量还有下面几个限制:
- 定义的时候必须初始化
- 只能用于定义局部变量
- 不能用于定义成员变量、方法参数、返回类型
- 不能在 Lambda 表达式中使用
其中不能在 Lambda参数使用var,这条限制在Java 11中得到了改进。Java 11 允许开发者在Lambda表达式中使用var进行参数声明。
下面我们回顾一下var声明局部变量的使用,如果只想看更新部分,可以直接看下面总结的第5部分:
可以使用关键字 var 声明局部变量,示例如下:
try {
URL url = new URL("http://www.oracle.com/");
URLConnection conn = url.openConnection();
Reader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
您可以通过使用var标识符声明局部变量来重写此示例。变量的类型是从上下文中推导出来的:
try {
var url = new URL("http://www.oracle.com/");
var conn = url.openConnection();
var reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
显然下面的代码更简练。
var是一个保留的类型名称,而不是关键字,这意味着使用var作为变量、方法或包名称的现有代码不受影响。但是,使用var作为类或接口名称的代码会受到影响,需要重命名该类或接口。
var可用于以下类型的变量:
- 带有初始值设定项的局部变量声明:
var list = new ArrayList<String>(); // 推导为 ArrayList<String>
var stream = list.stream(); // 推导为 Stream<String>
var path = Paths.get(fileName); // 推导为 Path
var bytes = Files.readAllBytes(path); // 推导为 bytes[]
- 增加循环中声明索引变量:
List<String> myList = Arrays.asList("礼", "乐", "射", "御", "书", "数"); //六艺
for (var element : myList) {...} // 推导为 String
- 传统for循环中声明索引变量:
for (var counter = 0; counter < 10; counter++) {...} // 推导为 int
- try with resources变量:
try (var input = new FileInputStream("唐宋八大家.txt")) {...} // 推导为 FileInputStream
- 隐式类型lambda表达式的形参声明:
BiFunction<Integer, Integer, Integer> = (a, b) -> a + b;
在JDK11及更高版本中,可以使用var标识符声明隐式类型lambda表达式的每个形参:
(var a, var b) -> a + b;
因此,隐式类型lambda表达式中的形参声明的语法与局部变量声明的语法一致;将var标识符应用于隐式类型lambda表达式中的每个形参,其效果与不使用var完全相同。
既然如此,那为什么我们要对lambda参数使用var?
与局部变量声明的语法保持一致的一个好处是:可以为lambda参数添加注解:
Function<String, String> msgWarpper = m -> "'" + m + "'";
Function<String, String> msgWarpper1 = (@NotNull var m) -> "'" + m + "'";
System.out.println(msgWarpper.apply("李商隐")); //结果 '李商隐'
System.out.println(msgWarpper1.apply("杜牧")); // 结果 '杜牧'
System.out.println(msgWarpper1.apply(null)); // 结果 'null' 这里notnull单独使用没有效果,还要加上其他处理才能生效
需要注意:不能在隐式类型的lambda表达式中混合隐式声明的参数和var声明的形参,也不能在显式类型的lambda表达式中混合var声明的正式参数和显式类型。以下示例是不允许的:
(var x, y) -> x.process(y) // 不能混合var声明和隐式类型声明
(var x, int y) -> x.process(y) // 不能混合var声明和显示类型声明
局部变量类型推导原则
局部变量声明可以通过消除冗余信息来提高代码的可读性。然而,它也会通过省略有用的信息来降低代码的可读性。因此,要根据实际情况来判断是否应该使用此功能;关于何时应该使用和不应该使用,没有严格的规则。
局部变量声明不是孤立存在的;周围的代码可能会影响甚至压倒var声明的效果。局部变量类型推导:风格指南 检查了周围代码对var声明的影响,解释了显式和隐式类型声明之间如何权衡,可以为有效使用var声明提供指导。
2、新增String API
String 是 Java 中最常用的一个类,Java 11 为了更好地处理字符串,新增了几个新的 API:
| 方法名 | 描述 |
|---|---|
| isBlank() | 检查字符串是否为空或仅包含空白字符 |
| lines() | 分割获取字符串流(Stream) |
| strip() | 去除字符串首尾的空白字符 |
| repeat(n) | 复制字符串 |
isBlank()
用于判断字符串是否为空或者只包含空白字符。
"".isBlank(); // true
" ".isBlank(); // true
" \n\t ".isBlank() // true
"大音希声".isBlank(); // false
该方法是对isEmpty()的补充,isEmpty() 只能检查字符串长度是否为0,而isBlank()则回进一步检查了字符串的内容是否也只包含空白字符。
lines()
该方法返回一个流(Stream),该流由字符串中的行组成,使用行结束符作为分隔符。
"象形\n指事\n会意\n形声\n转注\n假借".lines().forEach(System.out::println);
执行结果:
象形
指事
会意
形声
转注
假借
该方法可以处理不同平台上的换行符,无论是\n,\r还是\r\n。
strip()
去除字符串首尾的空白字符。如果字符串中间有空白字符,它们不会被去掉。
System.out.println(" 汉字六书 ".strip());
执行结果:
汉字六书
在原来老的 Java 版本中有一个方法:trim(),该方法也可以去掉空格,但是它只能去掉除半角空格。看下面例子
// 全角
System.out.printf("strip[全角] :%s%n", "[" + " 汉字 六书 ".strip() + "]"); // [汉字 六书]
System.out.printf("trim[全角] :%s%n", "[" + " 汉字 六书 ".trim() + "]"); // [ 汉字 六书 ]
// Unicode空白字符
char ch = '\u2000'; // Unicode空白字符
System.out.printf("strip[Unicode空白字符]:%s%n", "[" + (ch + "汉字 六书" + ch).strip() + "]");
System.out.printf("trim[Unicode空白字符] :%s%n", "[" + (ch + "汉字 六书" + ch).trim() + "]");
// 半角
System.out.printf("strip[半角] :%s%n", "[" + " 汉字 六书 ".strip() + "]");
System.out.printf("trim[半角] :%s%n", "[" + " 汉字 六书 ".trim() + "]");
// tab键、换行符
System.out.printf("strip[tab键、换行符] :%s%n", "[" + "\n汉字 六书\t".strip() + "]");
System.out.printf("trim[tab键、换行符] :%s%n", "[" + "\n汉字 六书\t".trim() + "]");
运行结果:
strip[全角] :[汉字 六书]
trim[全角] :[ 汉字 六书 ]
strip[Unicode空白字符]:[汉字 六书]
trim[Unicode空白字符] :[ 汉字 六书 ]
strip[半角] :[汉字 六书]
trim[半角] :[汉字 六书]
strip[tab键、换行符] :[汉字 六书]
trim[tab键、换行符] :[汉字 六书]
trim()早在Java早期就存在,当时Unicode还没有完全发展到我们今天广泛使用的标准。
trim()方法移除字符串两侧的空白字符(空格、tab键、换行符),无法删除掉Unicode空白字符,但用Character.isWhitespace方法可以判断出来
所以:
- trim() 只移除ASCII字符集中定义的空白字符。
- strip() 移除所有Unicode字符集中定义的空白字符。
strip() 还有两个方法:
- stripLeading():仅移除开头的空白字符。
- stripTrailing():仅移除末尾的空白字符。
这里就不具体介绍了
repeat()
将字符串重复指定的次数,并将这些重复的字符串连接为一个新的字符串。
System.out.println("汉字六书;".repeat(6));
运行结果:
汉字六书;汉字六书;汉字六书;汉字六书;汉字六书;汉字六书;
注意
- 如果传入的次数为0,结果是一个空字符串。
- 如果次数小于0,会抛出IllegalArgumentException。
3、新增Files API
Java 11 对java.nio.file.Files新增了三个 API:
- readString():读取文件内容
- writeString():写入文件内容
- isSameFile():比较两个路径是否指向文件系统中的同一个文件
这三个 API 简化了我们对文件的操作,使得操作文件变得更加简便了。
writeString()
该方法可以将字符串写入文件中。
public static Path writeString(Path path, CharSequence csq, OpenOption... options) throws IOException
public static Path writeString(Path path, CharSequence csq, Charset cs, OpenOption... options) throws IOException
第一个方法等效于writeString(path, csq, StandardCharsets.UTF_8, options)。
@Test
public void writeStringTest() throws IOException {
Path path = Paths.get("file.txt");
String content = "“谷”原来是指有壳的粮食;像稻、稷(jì计,即谷子)、黍(亦称黄米)等外面都有一层壳,所以叫做谷。谷字的音,就是从壳的音来的。五谷原是中国古代所称的五种谷物,后泛指粮食类作物。关于五谷主要有两种说法,主流的是稻(俗称水稻、大米)、黍(shǔ,俗称黄米)、稷(jì,又称粟,俗称小米)、麦(俗称小麦,制作面粉用)、菽(俗称大豆)。因为有的地方气候干旱,不利于水稻的种植,因此有将麻(俗称麻子)代替稻,作为五谷之一。";
Files.writeString(path, content);
}
readString()
该方法可以读取文件的全部内容,返回一个字符串。有2个重载方法。
第一种方法将文件中的所有内容读取为字符串,并使用 UTF-8 字符集将其从字节解码为字符。该方法可确保在所有内容均已关闭时关闭文件读取或抛出I/O错误或其他运行时异常。 第二种方法与仅使用指定字符集的方法相同。
请注意,这些方法不适用于读取非常大的文件。否则,如果文件过大,例如,文件可能会抛出 OutOfMemoryError。
public static String readString(Path path) throws IOException
public static String readString(Path path, Charset cs) throws IOException
第一个方法等效于readString(path,StandardCharsets.UTF_8)。
@Test
public void readStringTest() throws IOException {
Path path = Paths.get("file.txt");
System.out.println(path.toAbsolutePath()); // 打印路径
String content = Files.readString(path);
System.out.println(content);
}
isSameFile()
该方法用于比较两个路径是否指向文件系统中的同一个文件,可以用来检查符号链接或文件快捷方式是否指向同一个文件。
@Test
public void isSameFileTest() throws IOException {
Path path1 = Paths.get("file_01.txt");
Path path2 = Paths.get("file_02.txt");
boolean result = Files.isSameFile(path1,path2);
System.out.println(result);
}
文件路径相同返回 true,否则返回 false。
4、JEP 330:运行单文件源码程序
增强java启动器以运行作为java源代码的单个文件提供的程序,包括通过“shebang”文件和相关技术在脚本中使用。
这个功能尤其适合初学Java编程的新手,或想迅速体验编写简短代码的用户。此功能与 jshell 一起,将成为Java初学者的重要助手。对于有经验的开发者,它可以用来简化代码验证和测试工具。
这项特性允许开发者直接运行一个包含源代码的单个Java文件,而无需事先将其编译成字节码。
- 此功能允许使用 Java 解释器直接执行 Java 源代码。
- 源代码在内存中编译,然后由解释器执行。但是有个限制条件是所有相关的类必须定义在同一个 Java 文件中。
我们可以使用 Java 命令直接运行一个单文件的Java源代码,比如我们有一个名为 LaunchSourceTest.java 的文件,内容如下:
public class LaunchSourceTest {
public static void main(String[] args) {
System.out.println("天地玄黄,宇宙洪荒!");
}
}
我们可以使用下面命令来执行这个 Java 文件
cd F:\workspacenew2023-04\mylab\mytest\src\test\java\com\ld\mytest\test\java11[java文件所在目录]
# windows下修改编码,改为utf-8编码,因为上面使用了中文,不改编码会乱码。
F:\workspacenew2023-04\mylab\mytest\src\test\java\com\ld\mytest\test\java11>chcp 65001
java LaunchSourceTest.java
执行结果:
天地玄黄,宇宙洪荒!
传参
java java文件名 [参数……] #参数可以有多个,空格分隔,如果参数本身待空格,用引号包裹
public class LaunchSourceTest {
public static void main(String[] args) {
if (args == null || args.length < 1) {
System.err.println("谁说的?");
System.exit(1);
}
System.out.println(String.format("%s说:天地玄黄,宇宙洪荒!", args[0]));
}
}
运行
java LaunchSourceTest.java kk
执行结果:
kk说:天地玄黄,宇宙洪荒!
如果不传参
运行
java LaunchSourceTest.java
执行结果:
谁说的?
上面我们文件后缀是java,如果后缀不是java,那么jvm会用文件名+.class搜寻文件,找不到就会报错。当然也开业使用—source选项指定源文件:
java --source version[jdk版本] [java文件名]
# 如
java --source 11 LaunchSourceTest.java kk
一个文件中多个类
上面提到所有需要执行的代码是在同一个java文件中即可,这同一个文件中可以存在多个类。
下面这个java文件,在IDE中是编译不通过的,因为一个java文件中只能有一个public类。但是用java命令是可以运行的。
public class LaunchSourceTest {
public static void main(String[] args) {
BaijiaXing.write();
}
}
// 下面类的public去掉,在IDE中就不报错了
public class BaijiaXing {
public static void write() {
System.out.println("赵钱孙李,周吴郑王!");
}
public static void main(String[] args) {
System.out.println("第二个类的main方法");
}
}
运行
java LaunchSourceTest.java
执行结果:
赵钱孙李,周吴郑王!
执行的是第一个类的main方法,后面的不会执行。
Shebang文件
shebang,Linux中常见的符号。#!,其中she指代#,而bang指代!。
Shebang文件是Unix系统中常见的文件,以#!/path/executable(#!开头后面跟一个可执行文件路径)作为文件的开头第一行,shebang通常出现在类Unix系统的脚本中第一行,作为前两个字符,用于命令解释器声明,如:
#!/bin/sh
#!/bin/bash
如果脚本文件中没有#!这一行或者指定的不是一个可执行文件,那么执行时会默认采用当前Shell去解释这个脚本(即:$SHELL环境变量)。
我们来创建一个shebang文件:
在文件第一行增加
#!/path/java --source version
#!/home/ubuntu/jdk/jdk-11.0.12/bin/java --source 11
public class LaunchSourceTest {
public static void main(String[] args) {
BaijiaXing.write();
}
}
// 下面类的public去掉,在IDE中就不报错了
public class BaijiaXing {
public static void write() {
System.out.println("赵钱孙李,周吴郑王!");
}
public static void main(String[] args) {
System.out.println("第二个类的main方法");
}
}
将上面内容为LaunchSourceTest的文件中。按下面的方式来运行:
~$ cd /home/ubuntu/workdir[java文件所在路径]
~$ ./LaunchSourceTest
赵钱孙李,周吴郑王! #执行结果
5、JEP 321:标准 HTTP Client 升级
在Java 9的时候,引入了一个新的HTTP客户端API(HttpClient)作为作为实验特性,到了 Java 11,则被标准化并正式成为Java标准库的一部分。经过前面两个版本中的孵化,Http Client 几乎被完全重写,并且现在完全支持异步非阻塞。新版 Java 中,Http Client 的包名由 jdk.incubator.http 改为java.net.http,该 API 通过 CompleteableFutures 提供非阻塞请求和响应语义。
全新的 HTTP 客户端 API 具有如下几个优势:
- HTTP/2支持:全新的 HttpClient 支持 HTTP/2 协议的所有特性,包括同步和异步编程模型。
- WebSocket支持:支持WebSocket,允许建立持久的连接,并进行全双工通信。
- 同步和异步:提供了同步和异步的两种模式。这意味着我们既可以等待HTTP响应,也可以使用回调机制处理HTTP响应。
- 链式调用:新的HttpClient 允许链式调用,使得构建和发送请求变得更简单。
- 更好的错误处理机制:新的HttpClient 提供了更好的错误处理机制,当HTTP请求失败时,可以通过异常机制更清晰地了解到发生了什么。
下面是一个使用新的 HttpClient 示例:
@Test
public void testHttpRequest() {
try {
URI uri = URI.create("http://www.baidu.com/"); // new URI("https://www.baidu.com");
HttpClient httpClient = HttpClient.newBuilder()
// 超时时间10秒,
.connectTimeout(Duration.of(10, ChronoUnit.SECONDS))
//HTTP版本为HTTP/2
.version(HttpClient.Version.HTTP_2).build();
// 默认get请求,想用post,增加.POST()
HttpRequest request = HttpRequest.newBuilder(uri).build();
// 1、同步调用
HttpResponse<String> response = httpClient.send(request, BodyHandlers.ofString());
int statusCode = response.statusCode();
String body = response.body();
System.out.println(statusCode);
System.out.println(body);
// 2、异步调用
// 使用 sendAsync() 方法发送异步请求,发送请求后会立刻返回 CompletableFuture,使用CompletableFuture中的方法来设置异步处理器
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()) // 第二个参数,这里是使用了返回字符串,可以返回数组、行、输入流等
// 申请结果处理,这里是返回body
.thenApply(HttpResponse::body)
// 把结果打印
.thenAccept(System.out::println)
//
.join();
} catch (Exception e) {
e.printStackTrace();
}
}
异步处理如果想直接处理结果可以改成如下方式:
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()) // 第二个参数,这里是使用了返回字符串,可以返回数组,按行返回,或者返回流等
// 这里少了thenApply
// 把结果打印
.thenAccept(response -> {
System.out.println(response.statusCode());
System.out.println(response.body());
})
//
.join();
6、JEP 181:基于嵌套的访问控制
引入嵌套,这是一种访问控制上下文,与Java编程语言中嵌套类型的现有概念一致。
在Java SE 11中,Java虚拟机支持将类和接口排列到一个新的访问控制上下文中,称为嵌套。嵌套允许逻辑上属于同一代码实体但被编译为不同类文件的类和接口访问彼此的私有成员,而不需要编译器插入可访问性扩展的桥接方法。嵌套是Java SE平台的一种低级机制;Java编程语言的访问控制规则没有变化。javac编译器已更新为在编译Java源代码中的嵌套类和接口时使用嵌套,方法是生成新的类文件属性,将顶级类(或接口)及其所有嵌套类和界面放置在同一嵌套中。Java虚拟机已更新为在检查私有构造函数、方法或字段的可访问性时使用这些属性,包括通过核心反射和java.lang.invoke.MethodHandles.LookupAPI。嵌套中的成员身份通过java.lang.Class的新的getNestHost和getNestMembers方法公开。
由于嵌套成员已经记录在顶级类或接口(嵌套宿主)的类文件中,因此该类文件必须在运行时存在,以便执行访问控制检查。这通常不是一个问题。但是在某些代码中,顶级类或接口仅充当嵌套类或接口的持有者,并且在其他方面未使用,打包工具可能已将该类文件从库或应用程序的分发中删除。使用基于嵌套的访问控制,如果任何嵌套类或接口需要访问彼此的私有成员,则不再可能移除顶级类或接口,否则将抛出NoClassDefFoundError或ClassNotFoundException异常。
在Java 11之前的版本中,编译后的class文件通过InnerClasses和Enclosing Method两种属性来帮助编译器确定源码的嵌套关系。每个嵌套的类都会被编译到自己的class文件中,不同类的文件通过上述两种属性相互连接。这两种属性对于编译器确定相互之间的嵌套关系已经足够了,但并不适用于访问控制。
你可以尝试编写一段包含内部类的代码,并将其编译成class文件,然后使用javap命令行工具进行分析。
在Java 11中,引入了两个新的属性:
- NestMembers属性,存在于主类中,用于标识其他已知的静态嵌套成员
- NestHost属性,存在于嵌套类中,用于标识出它的嵌套宿主类
示例:
import lombok.Data;
@Data
public class MyUser {
private String name; // 姓名
private Integer age; // 年龄
private Likes likes; // 爱好
@Data
class Likes {
private String name;
}
}
java8下分析
#首先进入编译后class所在的目录
cd F:\workspacenew2023-04\mylab\mytest-jdk8\target\test-classes\com\ld\mytest\test\java8\model
javap -version -verbose MyUser.class #-version是显示java版本,-verbose输出附加信息
javap -version -verbose MyUser$Likes.class
可以看到,里面使用InnerClasses
1.8.0_351
Classfile /F:/workspacenew2023-04/mylab/mytest-jdk8/target/test-classes/com/ld/mytest/test/java8/model/MyUser.class
Last modified 2024-2-21; size 2559 bytes
MD5 checksum ef1a5a4adf1ca0369982d28d52b60803
Compiled from "MyUser.java"
public class com.ld.mytest.test.java8.model.MyUser
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // com/ld/mytest/test/java8/model/MyUser
#2 = Utf8 com/ld/mytest/test/java8/model/MyUser
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 name
#6 = Utf8 Ljava/lang/String;
#7 = Utf8 age
#8 = Utf8 Ljava/lang/Integer;
…………
#88 = Methodref #68.#89 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#89 = NameAndType #67:#12 // toString:()Ljava/lang/String;
#90 = Utf8 ()V
#91 = Methodref #3.#92 // java/lang/Object."<init>":()V
#92 = NameAndType #74:#90 // "<init>":()V
#93 = Utf8 SourceFile
#94 = Utf8 MyUser.java
#95 = Utf8 InnerClasses
#96 = Class #97 // com/ld/mytest/test/java8/model/MyUser$Likes
#97 = Utf8 com/ld/mytest/test/java8/model/MyUser$Likes
#98 = Utf8 Likes
…………
SourceFile: "MyUser.java"
InnerClasses:
#98= #96 of #1; // Likes=class com/ld/mytest/test/java8/model/MyUser$Likes of class com/ld/mytest/test/java8/model/MyUser
如果likes属性改为匿名内部类,就可以看到EnclosingMethod属性
private Likes likes = new Likes() {
}; // 爱好
javap -version -verbose MyUser$1.class # $1表示匿名内部类,具体名称看编译后的class文件
添加图片注释,不超过 140 字(可选)
1.8.0_351
Classfile /F:/workspacenew2023-04/mylab/mytest-jdk8/target/test-classes/com/ld/mytest/test/java8/model/MyUser$1.class
Last modified 2024-2-21; size 652 bytes
MD5 checksum 5293f907720842654d0639ec57f28dbe
Compiled from "MyUser.java"
class com.ld.mytest.test.java8.model.MyUser$1 extends com.ld.mytest.test.java8.model.MyUser$Likes
minor version: 0
major version: 52
…………
#18 = Utf8 Lcom/ld/mytest/test/java8/model/MyUser$1;
#19 = Utf8 SourceFile
#20 = Utf8 MyUser.java
#21 = Utf8 EnclosingMethod
#22 = Class #23 // com/ld/mytest/test/java8/model/MyUser
#23 = Utf8 com/ld/mytest/test/java8/model/MyUser
#24 = Utf8 InnerClasses
#25 = Utf8 Likes
……
SourceFile: "MyUser.java"
EnclosingMethod: #22.#0 // com.ld.mytest.test.java8.model.MyUser
InnerClasses:
#1; //class com/ld/mytest/test/java8/model/MyUser$1
#25= #3 of #22; //Likes=class com/ld/mytest/test/java8/model/MyUser$Likes of class com/ld/mytest/test/java8/model/MyUser
java11下分析
#首先进入编译后class所在的目录
cd F:\workspacenew2023-04\mylab\mytest\target\test-classes\com\ld\mytest\test\java11\model
javap -version -verbose MyUser.class #-version是显示java版本,-verbose输出附加信息
javap -version -verbose MyUser$Likes.class
MyUser类:
11.0.12
Classfile /F:/workspacenew2023-04/mylab/mytest/target/test-classes/com/ld/mytest/test/java11/model/MyUser.class
Last modified 2024年2月21日; size 2911 bytes
MD5 checksum 8160947fbf244be031192bffb2e82830
Compiled from "MyUser.java"
public class com.ld.mytest.test.java11.model.MyUser
minor version: 0
major version: 55
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #1 // com/ld/mytest/test/java11/model/MyUser
super_class: #3 // java/lang/Object
interfaces: 0, fields: 3, methods: 11, attributes: 4
Constant pool:
#1 = Class #2 // com/ld/mytest/test/java11/model/MyUser
#2 = Utf8 com/ld/mytest/test/java11/model/MyUser
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 name
#6 = Utf8 Ljava/lang/String;
#7 = Utf8 age
#8 = Utf8 Ljava/lang/Integer;
#9 = Utf8 likes
#10 = Utf8 Lcom/ld/mytest/test/java11/model/MyUser$Likes;
…………
#100 = Utf8 java/lang/invoke/MethodHandles
#101 = Utf8 Lookup
#102 = Utf8 NestMembers
…………
SourceFile: "MyUser.java"
BootstrapMethods:
0: #90 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
Method arguments:
#91 MyUser(name=\u0001, age=\u0001, likes=\u0001)
InnerClasses:
#96= #94 of #1; // Likes=class com/ld/mytest/test/java11/model/MyUser$Likes of class com/ld/mytest/test/java11/model/MyUser
public static final #101= #97 of #99; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
NestMembers:
com/ld/mytest/test/java11/model/MyUser$Likes
MyUser$Likes类:
11.0.12
Classfile /F:/workspacenew2023-04/mylab/mytest/target/test-classes/com/ld/mytest/test/java11/model/MyUser$Likes.class
Last modified 2024年2月21日; size 1931 bytes
MD5 checksum 11567758dc2fc6a2cbfbe6ae950f6bc3
Compiled from "MyUser.java"
class com.ld.mytest.test.java11.model.MyUser$Likes
minor version: 0
major version: 55
flags: (0x0020) ACC_SUPER
this_class: #1 // com/ld/mytest/test/java11/model/MyUser$Likes
super_class: #3 // java/lang/Object
interfaces: 0, fields: 2, methods: 7, attributes: 4
…………
#64 = String #65 // MyUser.Likes(name=\u0001)
#65 = Utf8 MyUser.Likes(name=\u0001)
#66 = Utf8 InnerClasses
#67 = Class #68 // com/ld/mytest/test/java11/model/MyUser
#68 = Utf8 com/ld/mytest/test/java11/model/MyUser
#69 = Utf8 Likes
#70 = Class #71 // java/lang/invoke/MethodHandles$Lookup
#71 = Utf8 java/lang/invoke/MethodHandles$Lookup
#72 = Class #73 // java/lang/invoke/MethodHandles
#73 = Utf8 java/lang/invoke/MethodHandles
#74 = Utf8 Lookup
#75 = Utf8 NestHost
…………
SourceFile: "MyUser.java"
BootstrapMethods:
0: #63 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
Method arguments:
#64 MyUser.Likes(name=\u0001)
InnerClasses:
#69= #1 of #67; // Likes=class com/ld/mytest/test/java11/model/MyUser$Likes of class com/ld/mytest/test/java11/model/MyUser
public static final #74= #70 of #72; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
NestHost: class com/ld/mytest/test/java11/model/MyUser
可以看到主类中增加了NestMembers属性,嵌套类里增加了NestHost属性
7、JEP 309:动态类文件常量
为了使JVM对动态语言更具吸引力,Java 的第七个版本已将invokedynamic引入其指令集。
过 Java 开发人员通常不会注意到此功能,因为它隐藏在Java字节代码中。
通过使用invokedynamic,可以延迟方法调用的绑定,直到第一次调用。
例如,Java 语言使用该技术来实现Lambda表达式,这些表达式仅在首次使用时才显示出来。
这样做,invokedynamic已经演变成一种必不可少的语言功能。
Java 11引入了类似的机制,扩展了 Java 文件格式,以支持新的常量池:CONSTANT_Dynamic,它在初始化的时候,像 invokedynamic指令生成代理方法一样,委托给bootstrap方法进行初始化创建,对上层软件没有很大的影响,降低开发新形式的可实现类文件约束带来的成本和干扰。
8、JEP 320:移除 Java EE和CORBA模块
从Java SE平台和JDK中删除Java EE和CORBA模块。这些模块从Java SE 9开始就不推荐使用了,所以当升级到Java 11或更高的版本的话,务必要先更急以下内容相关的代码:
移除的模块:
- java.xml.ws (JAX-WS, plus the related technologies SAAJ and Web Services Metadata)
- java.xml.bind (JAXB,XML到Java对象的绑定)
- java.activation (JAF)
- java.xml.ws.annotation (服务注解)
- java.corba (CORBA)
- java.transaction (JTA)
- java.se.ee (Aggregator module for the six modules above)
- jdk.xml.ws (Tools for JAX-WS)
- jdk.xml.bind (Tools for JAXB)
移除的工具:
jdk.xml.ws 模块中移除了以下JAX-WS 工具:
- wsgen
- wsimport
jdk.xml.bind 模块移除了以下JAXB 工具:
- schemagen
- xjc
java.corba 模块移除以下CORBA 工具:
- idlj
- orbd
- servertool
- tnamesrv
更多内容参考:www.oracle.com/java/techno…
9、JEP 318:Epsilon—无操作垃圾回收器
这是一个实验性功能。
Epsilon GC是新的实验性无操作垃圾收集器。Epsilon GC只处理内存分配,不实现任何内存回收机制。它对性能测试很有用,可以对比其他GC的成本/效益。它可以用于在测试中方便地断言内存占用和内存压力。在极端情况下,它可能对寿命很短的作业有用,在这些作业中,内存回收将在JVM终止时发生,或者在低垃圾应用程序中获得最后一次丢弃延迟的改进。请参阅JEP 318中关于其使用和权衡的更多讨论。
10、JEP 333:ZGC:可伸缩低延迟垃圾收集器
这是一个实验性功能。
Z垃圾收集器,也称为ZGC,是一种可扩展的低延迟垃圾收集器。它旨在实现以下目标:
- 暂停时间不超过10毫秒
- 暂停时间不会随着堆或活动集的大小而增加
- 处理大小从几百兆字节到数万亿字节不等的堆
ZGC的核心是一个并发垃圾收集器,这意味着所有繁重的工作(标记、压缩、引用处理、字符串表清理等)都是在Java线程继续执行的同时完成的。这大大限制了垃圾回收对应用程序响应时间的负面影响。
ZGC作为一个实验特性被包括在内。因此,要启用它,-XX:+UnlockExperimentalVMOptions选项需要与-XX:+UseZGC选项结合使用。
ZGC的这个实验版本有以下限制:
-
它仅在Linux/x64上可用。
-
不支持使用压缩的oop和/或压缩的类点。默认情况下,-XX:+UseCompressedOops和-XX:+UseCompressedClassPointers选项处于禁用状态。启用它们不会有任何效果。
-
不支持类卸载。默认情况下,-XX:+ClassUnloading和-XX:+CClassUnloadingWithConcurrentMark选项处于禁用状态。启用它们不会有任何效果。
-
不支持将ZGC与Graal结合使用。
11、JEP 335:废弃 Nashorn JavaScript 引擎
弃用Nashorn JavaScript脚本引擎和API以及jjs工具,在未来的版本中将删除它们。
Nashorn JavaScript引擎实现、API和jjs shell工具已被弃用,可能会在未来的版本中删除。使用jdk.nashorn.api.scripting和jdk.naskorn.api.tree包中的类和接口的代码将从javac获得弃用警告。
Nashorn引擎(当被javax.script API或jrunscript工具使用时)以及jjs shell工具将打印关于弃用的警告消息。要禁用此运行时警告消息,用户可以包含新的Nashorn选项--no-deprecation-warning。这对于依赖于精确输出的兼容性脚本可能很有用(例如,避免警告破坏其预期的精确输出)。
Nashorn JavaScript引擎最初是在JDK 8 中引入的,用于取代Rhino脚本引擎,它允许在JVM上执行和调用JavaScript代码。
Java 11废弃Nashorn JavaScript引擎的主要原因是因为它难以跟上现代JavaScript发展的步伐,同时Oracle决定将资源集中在Java核心功能上,而不是维护与Java生态系统关联不大的组件,鼓励开发者转而使用更专注的JavaScript运行时环境,如Node.js。
12、JEP 332:支持传输层安全协议TLS 1.3
安全传输层协议(TLS)用于在 两个通信应用程序之间 提供保密性和数据完整性。
该协议由两层组成: TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)
Java 11 中包含了传输层安全性(TLS)1.3 规范(RFC 8446)的实现,替换了之前版本中包含的 TLS。
TLS 1.3,重新定义了以下新标准算法名称:
- TLS协议版本名称:TLSv1.3
- SSLContext算法名称:TLSv1.3
- TLS 1.3的TLS密码套件名称:TLS_AES_128_GCM_SHA256、TLS_AES_256_GCM_SHAD384
- X509KeyManager的keyType:RSASSA-PPS
- X509TrustManager的authType:RSASSA-PSS
还为 TLS 1.3 添加了一个新的安全属性 jdk.tls.keyLimits。当特定算法的指定量的数据已经被处理时,握手后密钥和IV更新被触发以导出新密钥。
添加了一个新的系统属性jdk.tls.server.protocols,用于在SunJSSE提供程序的服务器端配置默认启用的协议套件。
需要注意,KRB5密码套件实现已从JDK中删除,因为它们被认为不再安全。
同时注意,TLS 1.3与以前的版本不直接兼容。尽管TLS 1.3可以使用向后兼容模式实现,但升级到TLS 1.3时仍需要考虑几个兼容性风险:
TLS 1.3使用半关闭策略,而TLS 1.2和以前的版本使用双工关闭策略。对于依赖双工关闭策略的应用程序,升级到TLS 1.3时可能存在兼容性问题。
signature_algorithms_cert扩展要求将预定义的签名算法用于证书身份验证。然而,在实践中,应用程序可能使用不受支持的签名算法。
TLS 1.3中不支持DSA签名算法。如果服务器配置为仅使用DSA证书,则无法升级到TLS 1.3。
TLS 1.3支持的密码套件与TLS 1.2及以前的版本不同。如果应用程序对不再支持的密码套件进行硬编码,则可能无法在不修改应用程序代码的情况下使用TLS 1.3。
TLS 1.3会话恢复和密钥更新行为与TLS 1.2及以前的版本不同。兼容性影响应该是最小的,但如果应用程序依赖于TLS协议的握手细节,则可能存在风险。
如果需要,系统属性jdk.tls.client.procols和jdk.tls.server.protocols可用于在SunJSSE提供程序中相应地配置默认启用的协议。
13、JEP 328:飞行记录器
飞行记录器(Flight Recorder) 之前是Oracle JDK 中的商业插件,但在 Java 11 中,其代码被包含到公开代码库中,这样所有人都能使用该功能了。
JFR (Java Flight Recorder)是一种分析工具,用于从正在运行的 Java 应用程序中收集诊断和分析数据。它的性能开销可以忽略不计,通常低于 1%。 因此它可以用于生产应用程序。
Java 语言中的飞行记录器类似飞机上的黑盒子,是一种低开销的事件信息收集框架,主要用于对 应用程序和 JVM 进行故障检查、分析。飞行记录器记录的 主要数据源于应用程序、JVM 和 OS,这些事件信息 保存在单独的事件记录文件中,故障发生后,能够从事件记录文件中 提取出有用信息 对故障进行分析。
启用飞行记录器参数如下:
-XX:StartFlightRecording
也可以使用jdk的bin目录下的jcmd工具启动和配置飞行记录器:
飞行记录器启动、配置参数示例
$ jcmd <pid> JFR.start
$ jcmd <pid> JFR.dump filename=recording.jfr
$ jcmd <pid> JFR.stop
JFR 使用测试:
public class FlightRecorderTest extends Event {
@Label("Hello World")
@Description("Helps the programmer getting started")
static class HelloWorld extends Event {
@Label("Message")
String message;
}
public static void main(String[] args) {
HelloWorld event = new HelloWorld();
event.message = "hello, world!";
event.commit();
}
}
在运行时加上如下参数:
java -XX:StartFlightRecording=duration=1s, filename=recording.jfr
下面读取上一步中生成的 JFR 文件:recording.jfr
飞行记录器分析示例
public void readRecordFile() throws IOException {
final Path path = Paths.get("D:\java\recording.jfr");
final List<RecordedEvent> recordedEvents = RecordingFile.readAllEvents(path);
for (RecordedEvent event : recordedEvents) {
System.out.println(event.getStartTime() + "," + event.getValue("message"));
}
}
收集到的JFR记录文件可以使用多种工具进行分析,最常用的是JDK Mission Control(JMC)。JMC提供了一个图形界面,用于查看和分析JFR产生的数据文件。
14、移除的APIs、工具、容器
移除了Nashorn JavaScript 引擎、禁用偏向锁、移除 Solaris 和 SPARC 平台的支持
参考:
-
[JDK 15中移除的特性和容器](
Oracle JDK和OpenJDK之间的差异
尽管官方已经声明了让OpenJDK和Oracle JDK二进制文件尽可能接近的目标,但至少对于JDK 11来说,这两个选项之间仍然存在一些差异。
目前的差异是:
- 只有Oracle JDK提供Solaris,只有OpenJDK提供Alpine Linux。
- 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要求使用Oracle提供的证书对第三方加密提供程序进行签名。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. 使用受许可条款约束的说明(OTN(Oracle Technology Network License Agreement for Oracle Java SE )协议),OpenJDK源代码包含GPL协议。