既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
module store.api{
exports com.dingtalk.store.api;
}
使用module后,即使包中的类是public的,如果未通过exports显式导出其程序包,则外部模块是不可调用的。
如果一个模块想要使用被另一个模块导出的package包中的类,可以用requires关键字在其module-info.java文件中来导入(读取)目标模块的package包。
module store.service {
requires com.dingtalk.store.api;
}
Java9 module 与Maven module 很像,但功能完全不一样,后者是作为依赖构建来方便管理应用代码,而Java Module是在于安全性、访问性控制,通过exports/requires 控制模块内需要暴露和依赖的具体包。
接口支持定义私有方法
在 Java 8 中增加了默认方法,在 Java 9 中又增加了私有方法,这时开始接口中不仅仅有了定义,还具有了行为。我想这是出于代码构造上的考虑,如果没有私有方法,那么当多个默认方法的行为一样时,就要写多个相同的代码。而有了私有方法,事情就变得不一样了。
举个例子:
public class Jdk9Interface {
public static void main(String[] args) {
ChinaPeople chinaPeople = new ChinaPeople();
chinaPeople.sleep();
chinaPeople.eat();
chinaPeople.doXxx();
}
}
class ChinaPeople implements People {
@Override
public void sleep() {
System.out.println("躺着睡");
}
}
interface People {
void sleep();
default void eat() {
drink();
}
default void doXxx() {
drink();
}
private void drink() {
System.out.println("喝水");
}
}
例子中的接口 people 中的 eat() 和 doXxx() 默认行为一致,使用私有方法可以方便的抽取一个方法出来。
输出结果:
躺着睡
喝水
喝水
集合工厂方法
在 Java 9 中为集合的创建增加了静态工厂创建方式,也就是 of 方法,通过静态工厂 of 方法创建的集合是只读集合,里面的对象不可改变。并且不能存在 null 值,对于 set 和 map 集合,也不能存在 key 值重复。这样不仅线程安全,而且消耗的内存也更小。
// 工厂方法创建集合
List<String> stringList = List.of("a", "b", "c", "d");
Set<String> stringSet = Set.of("a", "b", "c", "d");
Map<String, Integer> stringIntegerMap = Map.of("key1", 1, "key2", 2, "key3", 3);
Map<String, Integer> stringIntegerMap2 = Map.ofEntries(Map.entry("key1", 1), Map.entry("key2", 2));
// 集合输出
System.out.println(stringList);
System.out.println(stringSet);
System.out.println(stringIntegerMap);
System.out.println(stringIntegerMap2);
输出:
[a, b, c, d]
[d, a, c, b]
{key2=2, key1=1, key3=3}
{key2=2, key1=1}
这种只读集合在 Java 9 之前创建是通过 Collections.unmodifiableList 修改集合操作权限实现的。
List<String> arrayList = new ArrayList<>();
arrayList.add("CSDN");
arrayList.add("阿提说说");
// 设置为只读集合
arrayList = Collections.unmodifiableList(arrayList);
静态工厂 of 方法创建的集合还有一个特性,就是工厂内部会自由复用已有实例或者创建新的实例,所以应该避免对 of 创建的集合进行判等或者 haseCode 比较等操作。
// 工厂可以自由创建新的实例或者复用现有实例,所以 使用 of 创建的集合,避免 == 或者 hashCode 判断操作
List<String> stringList = List.of("a", "b", "c", "d");
List<String> stringList2 = List.of("a", "b", "c", "d");
System.out.println(stringList.hashCode());
System.out.println(stringList2.hashCode());
// 输出结果
// 3910595
// 3910595
增强流(Stream)API
Stream 流操作自从 Java 8 引入以来,一直广受好评。
当然,学习 Stream 之前要先学习 Lambda ,也是Java 8的内容。
在 Java 9 中,又对 Stream 进行了增强,主要增加了 4 个新的操作方法:dropWhile,takeWhile,ofNullable,iterate。
1、takeWhile: 从头开始筛选,遇到不满足的就结束
// takeWhile ,从头开始筛选,遇到不满足的就结束了
List<Integer> list1 = List.of(1, 2, 3, 4, 5);
List<Integer> listResult = list1.stream().takeWhile(x -> x < 3).collect(Collectors.toList());
System.out.println(listResult);
// takeWhile ,从头开始筛选,遇到不满足的就结束
List<Integer> list2 = List.of(1, 2, 3, 4, 3, 0);
List<Integer> listResult2 = list2.stream().takeWhile(x -> x < 3).collect(Collectors.toList());
System.out.println(listResult2);
----------------------------------
输出结果:
[1, 2]
[1, 2]
2、dropWhile: 从头开始删除,遇到不满足的就结束
// dropWhile ,从头开始删除,遇到不满足的就结束
List<Integer> list1 = List.of(1, 2, 3, 4, 5);
List<Integer> listResult = list1.stream().dropWhile(x -> x < 3).collect(Collectors.toList());
System.out.println(listResult);
// dropWhile ,从头开始删除,遇到不满足的就结束
List<Integer> list2 = List.of(1, 2, 3, 4, 3, 0);
List<Integer> listResult2 = list2.stream().dropWhile(x -> x < 3).collect(Collectors.toList());
System.out.println(listResult2);
----------------------------------
输出结果:
[3, 4, 5]
[3, 4, 3, 0]
3、ofNullable: 创建支持全 null 的 Stream
Stream<Integer> stream = Stream.of(1, 2, null);
stream.forEach(System.out::print);
System.out.println();
// 空指针异常
// stream = Stream.of(null);
stream = Stream.ofNullable(null);
stream.forEach(System.out::print);
----------------------------------
输出结果:
12null
4、iterate: 可以重载迭代器。
IntStream.iterate(0, x -> x < 10, x -> x + 1).forEach(System.out::print);
----------------------------------
输出结果:
0123456789
在 Stream 增强之外,还增强了 Optional ,Optional 增加了可以转换成 Stream 的方法。
Stream<Integer> s = Optional.of(1).stream();
s.forEach(System.out::print);
HTTP / 2 Client
Java 9 内置了新的 HTTP/2 客户端,请求更加方便。
随便访问一个不存在的网页。
HttpClient client = HttpClient.newHttpClient();
URI uri = URI.create("http://www.baidu.com");
HttpRequest req = HttpRequest.newBuilder(uri).header("User-Agent", "Java").GET().build();
HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandler.asString());
String body = resp.body();
System.out.println(body);
输出得到的结果,这里是这个网站的报错信息。
There is no method xxxAction in ApiController
Java REPL - JShell
交互式的编程环境在其他语言如 Python 上早就有了,而 Java 上的交互式语言只到 Java 9 才出现。交互式的编程可以让开发者在输入代码的时候就获取到程序的运行结果,而不用像之前一样新建文件、创建类、导包、测试一系列流程。
JShell 中支持 tab 补全代码以及自动添加分号,下面通过一个例子演示 JShell 的使用。
1、进入 JShell. 查看帮助文档
C:\Users>jshell
| 欢迎使用 JShell -- 版本 9
| 要大致了解该版本, 请键入: /help intro
jshell> /help
| 键入 Java 语言表达式, 语句或声明。
| 或者键入以下命令之一:
| /list [<名称或 id>|-all|-start]
| 列出您键入的源
| /edit <名称或 id>
| 编辑按名称或 id 引用的源条目
| /drop <名称或 id>
| 删除按名称或 id 引用的源条目
| /save [-all|-history|-start] <文件>
| 将片段源保存到文件。
| /open <file>
| 打开文件作为源输入
| /vars [<名称或 id>|-all|-start]
| 列出已声明变量及其值
| /methods [<名称或 id>|-all|-start]
| 列出已声明方法及其签名
| /types [<名称或 id>|-all|-start]
| 列出已声明的类型
| /imports
| 列出导入的项
| /exit
| 退出 jshell
| /env [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>] ...
| 查看或更改评估上下文
| /reset [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>]...
| 重启 jshell
| /reload [-restore] [-quiet] [-class-path <路径>] [-module-path <路径>]...
| 重置和重放相关历史记录 -- 当前历史记录或上一个历史记录 (-restore)
| /history
| 您键入的内容的历史记录
| /help [<command>|<subject>]
| 获取 jshell 的相关信息
| /set editor|start|feedback|mode|prompt|truncation|format ...
| 设置 jshell 配置信息
| /? [<command>|<subject>]
| 获取 jshell 的相关信息
| /!
| 重新运行上一个片段
| /<id>
| 按 id 重新运行片段
| /-<n>
| 重新运行前面的第 n 个片段
|
| 有关详细信息, 请键入 '/help', 后跟
| 命令或主题的名称。
| 例如 '/help /list' 或 '/help intro'。主题:
|
| intro
| jshell 工具的简介
| shortcuts
| 片段和命令输入提示, 信息访问以及
| 自动代码生成的按键说明
| context
| /env /reload 和 /reset 的评估上下文选项
jshell>
2、定义一个变量:a = 10,遍历从 0 到 a 的数字
jshell> int a =10;
a ==> 10
jshell> for(int i=0;i<a;i++){System.out.println(i);}
0
1
2
3
4
5
6
7
8
9
3、定义一个集合,赋值 1,2,3,4,5。然后输出集合
jshell> List list = List.of(1,2,3,4,5);
list ==> [1, 2, 3, 4, 5]
jshell> list
list ==> [1, 2, 3, 4, 5]
4、查看输入过的代码
jshell> /list
1 : int a =10;
2 : for(int i=0;i<a;i++){System.out.println(i);}
3 : List list = List.of(1,2,3,4,5);
4 : list
5、列出导入的包
jshell> /imports
| import java.io.\*
| import java.math.\*
| import java.net.\*
| import java.nio.file.\*
| import java.util.\*
| import java.util.concurrent.\*
| import java.util.function.\*
| import java.util.prefs.\*
| import java.util.regex.\*
| import java.util.stream.\*
6、将代码保存到文件并退出
jshell> /save d:/JShell.java
jshell> /exit
再见
在 D 盘看到的保存的代码片段。
JVM 调优的新特性
第一个:删除 JDK 8 中已弃用的垃圾收集器 (GC) 组合
-
这意味着以下 GC 组合不再存在:
- DefNew + CMS
- ParNew + SerialOld
- 增量CMS
-
并发标记扫描 (CMS) 的“前台”模式也已被删除。以下命令行标志已被删除:
- -Xincgc
- -XX:+CMSIncrementalMode
- -XX:+UseCMSCompactAtFullCollection
- -XX:+CMSFullGCsBeforeCompaction
- -XX:+UseCMSCollectionPassing
-
命令行标志-XX:+UseParNewGC不再有效。ParNew 只能与 CMS 一起使用,而 CMS 需要 ParNew。因此,该-XX:+UseParNewGC标志已被弃用,并且可能会在未来的版本中被删除。
第二个:使 G1 成为默认垃圾收集器
- 使垃圾优先 (G1) 成为 32 位和 64 位服务器配置上的默认垃圾收集器 (GC)。对于大多数用户来说,使用低暂停收集器(例如 G1)比以前默认的面向吞吐量的收集器(例如 Parallel GC)提供更好的整体体验。
- 增强垃圾优先 (G1) 垃圾收集器以自动确定几个重要的内存回收设置。以前必须手动设置这些设置以获得最佳结果。此外,修复了 G1 垃圾收集器的可用性、确定性和性能问题。
- 请参阅Java 平台中的 Garbage-First Garbage Collector ,标准版 HotSpot 虚拟机垃圾收集调优指南
第三个:弃用并发标记扫描 (CMS) 垃圾收集器
- 弃用并发标记扫描 (CMS) 垃圾收集器。-XX:+UseConcMarkSweepGC使用该选项在命令行上请求时会发出警告消息。Garbage-First (G1) 垃圾收集器旨在替代 CMS 的大多数用途。
其他更新
Java 9 中增加或者优化的功能远不止这些,上面只是列举了常用的一些新特性,更多的新特性如:
- 不能使用下划线 _ 作为变量名,因为它是一个关键字。
- Javadoc 支持 HTML5 并且支持搜索功能。
- Nashorn 引擎升级,更好的支持 Javascript.
- String 存储结构变更从 char -> byte
- 多Jdk版本共存jar:在同一个Jar包可以包含多个Java版本的class文件,在不同Jdk环境下使用对应该 jdk 版本的 jar。(这对算是用户很友好的功能)
- …
新特性很多,感兴趣的可以自己了解下。
Java 9 全部的新特性,请看官网:[Java 平台,标准版 Oracle JDK 9 中的新增功能](Java 平台,标准版 Oracle JDK 9 中的新增功能)
Java 10 新特性
自从 Java 9 开始,Oracle 调整了 Java 版本的发布策略,不再是之前的 N 年一个大版本,取而代之的是 6 个月一个小版本,三年一个大版本,这样可以让 Java 的最新改变迅速上线,而小版本的维护周期缩短到下个版本发布之前,大版本的维护周期则是 3 年之久。而 10 就是这么一个小版本,因为 Java 的后续版本基本都会包含之前新特性,所以还是把 Java 10 带来的改变单独写一写(不全)。
Java 10 全部的新特性,请看官网:JDK 10 发行说明
Java各个版本的文档入口:Java平台,标准版文档
Java各个版本下载:https://jdk.java.net/archive/
基于时间的版本号
就像上面说的,Java 调整了发布策略,为了适应这种发布节奏,随着改变的还有 Java 版本号的记录方式。
版本号的新模式是: **F
E
A
T
U
R
E
.
FEATURE.
FEATURE.INTERIM.
U
P
D
A
T
E
.
UPDATE.
UPDATE.PATCH**
- $FEATURE :基于发布版本,如 Java 10 的 10 。
- $INTERIM :问题修复和功能增强时 + 1,默认是 0 。
- $UPDATE :在进行兼容更新,修复新功能安全问题时 +1。
- $PATCH :特殊问题修复时 +1。
查看自己的 Java 10 版本。
$ java -version
java version "10.0.1" 2018-04-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)
局部类型推断
JEP 286 提案让 Java 增加了局部类型推断(Local-Variable Type Inference)功能,这让 Java 可以像 Js 里的 var 或者其他语言的 auto 一样可以自动推断数据类型。这其实只是一个新的语法糖,底层并没有变化,在编译时就已经把 var 转化成具体的数据类型了,但是这样可以减少代码的编写。
你可以像下面这样使用 var 语法。
var hashMap = new HashMap<String, String>();
hashMap.put("CSDN","阿提说说");
var string = "hello java 10";
var stream = Stream.of(1, 2, 3, 4);
var list = new ArrayList<String>();
如果你反编译编译后的这段代码,你会发现还是熟悉的代码片段。
HashMap<String, String> hashMap = new HashMap();
hashMap.put("微信", "wlw");
String string = "hello java 10";
Stream<Integer> stream = Stream.of(1, 2, 3, 4);
ArrayList<String> list = new ArrayList();
var 看似好用,其实也有很多限制,官方介绍了 var 只能用于下面的几种情况。
- 仅限带有初始化的程序的局部变量。
- for 循环或者增强for 循环中。
- for 循环中的声明。
public static void testVar() {
// 情况1,没有初始化会报错
// var list;
var list = List.of(1, 2, 3, 4);
// 情况2
for (var integer : list) {
System.out.println(integer);
}
// 情况3
for (var i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
尽管对 var 的使用场景增加了很多限制,但在实际使用时你还是要注意,就像下面的代码,你可能一眼并不能看出 result 的数据类型。
var query = "xxx";
var result = dbUtil.executeQuery(query);
基于 Java 的 JIT 编译器(实验性)
这个功能让基于 Java 开发的 JIT 编译器 **Graal **结合 Java 10 用在 Linux /x64 平台上,这是一个实验性的 JIT 编译器,有人说这也是 Java 10 中最具有未来感的引入。Graal 其实在 Java 9 中就已经引入了,它带来了 Java 中的 AOT (Ahead Of Time)编译,还支持多种语言,如 Js、Python、Ruby、R、以及其他基于 JVM (如 Java、Kotlin)的和基于 LLVM (如 C、C++)的语言。
想切换到 Graal 可以使用下面的 jvm 参数。
-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler
这里面有一点我觉得很有意思,看这个图。
这就很有意思了,Graal 是 Java 语言编写的,用 Java 编写的编译器,然后用来将 Java 字节码编译机器代码。
Graal 官网:https://www.graalvm.org/(opens new window)
类数据共享
JVM 启动时有一步是需要在内存中加载类,而如果有多个 jar,加载第一个 jar 的速度是最慢的。这就延长了程序的启动时间,为了减少这个时间,Java 10 引入了应用程序类数据共享(CDS)机制,它可以把你想共享的类共享在程序之间,使不同的 Java 进程之间共享这个类来减少这个类占用的空间以及加载速度。
G1并行全GC
早在 Java 9 时就已经引入了 G1 垃圾收集器,G1 的优点很多。而在 Java 10 中还是做了小小调整,当 G1 的并发收集线程不能快速的完成全 GC 时,就会自动切换到并行收集,这可以减少在最坏情况下的 GC 速度。
Unicode 语言标签扩展
这个提案让 JDK 实现了最新的 LDML 规范 (opens new window)中指定的更多的扩展。
主要增加了下面几个扩展方法。
java.time.temporal.WeekFields::of
java.util.Calendar::{getFirstDayOfWeek,getMinimalDaysInWeek}
java.util.Currency::getInstance
java.util.Locale::getDisplayName
java.util.spi.LocaleNameProvider
java.text.DateFormat::get\*Instance
java.text.DateFormatSymbols::getInstance
java.text.DecimalFormatSymbols::getInstance
java.text.NumberFormat::get\*Instance
java.time.format.DateTimeFormatter::localizedBy
java.time.format.DateTimeFormatterBuilder::getLocalizedDateTimePattern
java.time.format.DecimalStyle::of
测试:
Currency chinaCurrency = Currency.getInstance(Locale.CHINA);
Currency usCurrency = Currency.getInstance(Locale.US);
System.out.println("本地货币:" + chinaCurrency);
System.out.println("US.货币:" + usCurrency);
String displayName = Locale.getDefault().getDisplayName();
String displayLanguage = Locale.getDefault().getDisplayLanguage();
String displayCountry = Locale.getDefault().getDisplayCountry();
System.out.println("本地名称:" + displayName);
System.out.println("本地语言:" + displayLanguage);
System.out.println("本地国家:" + displayCountry);
int firstDayOfWeek = Calendar.getInstance().getFirstDayOfWeek();
System.out.println("本地每周第一天:" + firstDayOfWeek);
结果:
本地货币:CNY
US.货币:USD
本地名称:中文 (中国)
本地语言:中文
本地国家:中国
本地每周第一天:1
API更新
Java 10 删除了部分 API,也增加了一些实用方法。比如可以通过 Collection.copyOf 复制得到一个不可改变集合,即使原来的集合元素发生了变化也不会有影响。
var list = new ArrayList<String>();
list.add("wechat");
list.add("wlw");
List<String> copyList = List.copyOf(list);
list.add("test");
System.out.println(copyList);
// result
// [wechat, wn8398]
也为 Optional 增加了一个新的方法 orElseThrow。调用这个方法也可以获取到 optional 中的 value , 但是如果 value 为 null ,就会抛出异常。
另外在 Stream 最后收集数据的时候,Collectors 可以直接指定收集的集合为不可变集合,像下面这样。
list.stream().collect(Collectors.toUnmodifiableList());
list.stream().collect(Collectors.toUnmodifiableSet());
其他更新
Java 10 的更新内容不止这些,上面只是列举了常用的以及比较有意思的新特性。还有部分更新如:
1、JEP 312:Thread-Local Handshakes,JVM 内部功能,可以提高 JVM 性能。
2、JEP 313:删除了 javah 工具,说是删除,其实功能已经包含在 Java 8 中的 javac 里。
3、JEP 316:让 JVM 可以在备用的存储设备(如 NV-DIMM)上分配堆内存,而不用更改程序代码。
4、JEP 319:在 JDK 中提供一组默认的根证书颁发机构(CA)证书。
Java 10 全部的新特性,请看官网:JDK 10 发行说明
Java 11 新特性
Java 11 是 Java 8 之后的第一个 LTS 版本,但是也自从 Java 11 开始, Oracle JDK 不再可以免费的用于商业用途,当然如果你是个人使用,或者是使用 Open JDK ,那么还是可以免费使用的。
Java 11 全部的新特性,请看官网:JDK 11 发行说明
Java各个版本的文档入口:Java平台,标准版文档
Java各个版本下载:https://jdk.java.net/archive/
String API改动
字符串绝对是 Java 中最常用的一个类了,String 类的方法使用率也都非常的高,在 Java 11 中又为 String 类带来了一系列的好用操作。
- isBlank()判空
// 判空,blank里我放入了全角空格,半角空格,TAB
String blank = " ";
System.out.println(blank.isBlank());
// 输出
// true
- lines()分割获取字符串流
// lines 返回一个 Stream
String line = "a\nb\nc";
Stream<String> lines = line.lines();
// 使用 lambda 遍历
lines.forEach(System.out::println);
// 输出
// a
// b
// c
- repeat()复制字符串
// 复制字符串
String repeat = "CSDN/WX-阿提说说";
String repeat3 = repeat.repeat(3);
System.out.println(repeat3);
// 输出
// CSDN/WX-阿提说说CSDN/WX-阿提说说CSDN/WX-阿提说说
- strip()去除前后空白字符
// 去除前后空白
String strip = " https://www.baidu.com ";
System.out.println("==" + strip.trim() + "==");
// 去除前后空白字符,如全角空格,TAB
System.out.println("==" + strip.strip() + "==");
// 去前面空白字符,如全角空格,TAB
System.out.println("==" + strip.stripLeading() + "==");
// 去后面空白字符,如全角空格,TAB
System.out.println("==" + strip.stripTrailing() + "==");
// 输出
// == https://www.baidu.com ==
// ==https://www.baidu.com==
// ==https://www.baidu.com ==
// == https://www.baidu.com==
File API改动
读写文件变得更加方便
// 创建临时文件
Path path = Files.writeString(Files.createTempFile("test", ".txt"), "https://www.baidu.com");
System.out.println(path);
// 读取文件
String s = Files.readString(path);
System.out.println(s);
// 结果
//C:\Users\ADMINI~1\AppData\Local\Temp\test10960104919895436673.txt
//https://www.baidu.com
HTTP Client
在 Java 11 中 Http Client API 得到了标准化的支持。且支持 HTTP/1.1 和 HTTP/2 ,也支持 websockets。
你可以像这样发起一个请求:
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.hao123.com"))
.build();
// 异步
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
// 同步
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
更多的如同步异步请求,并发访问,设置代理等方式,可以参考 OpenJDK 官方文档:
http://openjdk.java.net/groups/net/httpclient/recipes-incubating.html(opens new window)
Lambda 局部变量推断
在 Java 10 中引入了 var 语法,可以自动推断变量类型。在 Java 11 中这个语法糖可以在 Lambda 表达式中使用了。
var hashMap = new HashMap<String, Object>();
hashMap.put("CSDN", "阿提说说");
hashMap.put("website", "https://blog.csdn.net/weixin\_40972073");
hashMap.forEach((var k, var v) -> {
System.out.println(k + ": " + v);
});
//输出
//website: https://blog.csdn.net/weixin\_40972073
//CSDN: 阿提说说
这里需要注意的是,(var k,var v) 中,k 和 v 的类型要么都用 var ,要么都不写,要么都写正确的变量类型。而不能 var 和其他变量类型混用。
单命令运行Java
自从学习 Java 的第一天,我们就知道运行一个 Java 文件,要先用 javac 命令编译,再用 java 命令运行,而现在只要一个 java 命令就可以运行了。
public class MainTest {
public static void main(String[] args) {
System.out.println("CSDN: 阿提说说");
}
}
//java -Dfile.encoding=UTF-8 MainTest.java
//CSDN: 阿提说说
免费的飞行记录器
商业版 JDK 中一直有一款低开销的事件信息收集工具,也就是飞行记录器(Java Flight Recorder),它可以对 JVM 进行检查、分析、记录等。当出现未知异常时可以通过记录进行故障分析。这个好用的工具在 Java 11 中将开源免费。所有人都可以使用这个功能了。
其他更新
- JEP 309 - 添加动态文件常量。
- JEP 318 - 添加 Epsilon 垃圾收集器。
- JEP 320 - 删除 Java EE 和 corba 模块(java.xml.ws, java.xml.bind, java.activation, java.xml.ws.annotation, java.corba, java.transaction, java.se.ee, jdk.xml.ws, jdk.xml.bind)。
- JEP 329 - 增加加密算法 chacha20,poly1305 的实现。
- JEP 333 - 引入实验性的 ZGC 垃圾收集器,保证停摆时间不会超过 10ms。
- JEP 335 - 废弃 Nashorn JavaScript 引擎
Java 11 全部的新特性,请看官网:JDK 11 发行说明
Java 12 新特性
Java 12 早在 2019 年 3 月 19 日发布,它不是一个长久支持(LTS)版本。此篇文章写一下部分Java 12的新特性。
Java 12 全部的新特性,请看官网:JDK 12 发行说明
Java各个版本的文档入口:Java平台,标准版文档
Java各个版本下载:https://jdk.java.net/archive/
Switch 表达式 改进
在 Java 12 中,对 Switch 表达式的写法进行了改进,虽然是一个语法糖的改进,也让 Switch 的代码编写变得更加优雅。先看一下在 Java 12 之前的 Switch 的写法。
由于 Switch 表达式在 Java 12 中并不是一个正式发布的功能,还处于预览测试阶段,所以想要使用 Java 12 去编译运行就需要打开功能预览参数,当然,如果你使用的是 Java 14 以及更高版本,就可以直接跳过这个部分了。
# 编译时
./bin/javac --enable-preview -source 12 ./Xxx.java
# 运行时
./bin/java --enable-preview Xxx
Java 12以前的switch写法
// 通过传入月份,输出月份所属的季节
public static void switchJava12Before(String day) {
switch (day) {
case "march":
case "april":
case "may":
System.out.println("春天");
break;
case "june":
case "july":
case "august":
System.out.println("夏天");
break;
case "september":
case "october":
case "november":
System.out.println("秋天");
break;
case "december":
case "january":
case "february":
System.out.println("冬天");
break;
}
}
Java 12的写法
public static void switchJava12(String day) {
switch (day) {
case "march", "april", "may" -> System.out.println("春天");
case "june", "july", "august" -> System.out.println("夏天");
case "september", "october", "november" -> System.out.println("秋天");
case "december", "january", "february" -> System.out.println("冬天");
}
}
另外还可使用返回值赋值
String season = switch (day) {
case "march", "april", "may" -> "春天";
case "june", "july", "august" -> "夏天";
case "september", "october", "november" -> "秋天";
case "december", "january", "february" -> "冬天";
default -> {
//throw new RuntimeException("day error")
System.out.println("day error");
break "day error";
}
};
System.out.println("当前季节是:" + season);
文件对比 Files.mismatch
在 Java 中对于文件的操作已经在 Java 11 中进行了一次增强,这次 Java 12 又带来了文件对比功能。
对比两个文件,如果内容一致,会返回 -1 ,如果内容不同,会返回不同的字节开始位置。
// 创建两个文件
Path pathA = Files.createFile(Paths.get("a.txt"));
Path pathB = Files.createFile(Paths.get("b.txt"));
// 写入相同内容
Files.write(pathA,"abc".getBytes(), StandardOpenOption.WRITE);
Files.write(pathB,"abc".getBytes(), StandardOpenOption.WRITE);
long mismatch = Files.mismatch(pathA, pathB);
System.out.println(mismatch);
// 追加不同内容
Files.write(pathA,"123".getBytes(), StandardOpenOption.APPEND);
Files.write(pathB,"321".getBytes(), StandardOpenOption.APPEND);
mismatch = Files.mismatch(pathA, pathB);
System.out.println(mismatch);
// 删除创建的文件
pathA.toFile().deleteOnExit();
pathB.toFile().deleteOnExit();
// RESULT
// -1
// 3
Compact Number
简化的数字格式可以直接转换数字显示格式,比如 1000 -> 1K,1000000 -> 1M 。
System.out.println("Compact Formatting is:");
NumberFormat upvotes = NumberFormat.getCompactNumberInstance(new Locale("en", "US"), Style.SHORT);
System.out.println(upvotes.format(100));
System.out.println(upvotes.format(1000));
System.out.println(upvotes.format(10000));
System.out.println(upvotes.format(100000));
System.out.println(upvotes.format(1000000));
// 设置小数位数
upvotes.setMaximumFractionDigits(1);
System.out.println(upvotes.format(1234));
System.out.println(upvotes.format(123456));
System.out.println(upvotes.format(12345678));
输出:
100
1K
10K
100K
1M
1.2K
123.5K
12.3M
JVM 相关更新
Shenandoah 垃圾收集器
Java 12 增加了 Shenandoah 一个低停顿的垃圾收集器,它可以和 Java 应用程序中的执行线程同时进行,用来收集垃圾进行内容回收,这样就可以让停顿时间更少。
更多关于 Shenandoah 垃圾收集器的介绍可以查看文档:Shenandoah GC 介绍 (opens new window)。
ZGC 并发类卸载
Z 垃圾收集器现在支持类卸载,通过卸载不使用的类来释放这些类相关的数据结构,从而减少应用程序的总体占用空间。因为是并发执行,所以不会停止 Java 应用程序线程的执行,也因此对 GC 的暂停时间影响微乎其微。默认情况下启用此功能,但可以使用命令行选项禁用** -XX:-ClassUnloading**。
JVM 常量 API
在包** java.lang.invoke.constant** 中定义了一系列的基于值的符号引用,可以用来描述各种可加载常量。可以更容易的对关键类文件和运行时构建的名义描述进行建模,特别是对那些从常量池中加载的常量,也让开发者可以更简单标准的处理可加载常量。
默认使用类数据共享(CDS)
这已经不是 JDK 第一次改进 CDS(Class Data Sharing) 功能了,CDS 可以让 JVM 在同一台机器或虚拟机上启动多个应用的速度速度大大增加。原理是在启动应用时共享一些类加载信息,这样启动新进程时就可以使用共享的数据。在 Java 12 之前此功能需要手动开启,Java 12 调整为默认开启。
微基准套件
Java 12 中添加一套新的基于 JMH 的基本的微基准测试套件。
JMH 的使用,可以参考文章 JMH - Java 代码性能测试的终极利器 (opens new window)。
其他更新
在 Java 11 支持了 Unicode 10 之后, Java 12 支持了 Unicode 11,支持操作更多的表情、符号。
Java 13 新特性
Java 13 早在 2019 年 9 月就已经发布,虽然不是长久支持版本,但是也带来了不少新功能。此篇文章写一下部分Java 13的新特性。
Java 13 全部的新特性,请看官网:JDK 13 发行说明
Java各个版本的文档入口:Java平台,标准版文档
Java各个版本下载:https://jdk.java.net/archive/
Switch 表达式 (二次预览)
在Java 12 中对 Switch 进行了一次增强,这次又对Switch进行了增强。
在 Java 13 中,又对 switch 表达式进行了增强,增加了 yield 关键词用于返回值,相比 break ,语义更加明确了。
public static String switchJava13(String month) {
return switch (month) {
case "march", "april", "may":
yield "春天";
case "june", "july", "august":
yield "夏天";
case "september", "october", "november":
yield "秋天";
case "december", "january", "february":
yield "冬天";
default:
yield "month error";
};
}
动态 CDS 存档
JVM 启动时有一步是需要在内存中加载类,而如果有多个 jar,加载第一个 jar 的速度是最慢的。这就延长了程序的启动时间,为了减少这个时间,Java 10 引入了应用程序类数据共享(CDS)机制,它可以把你想共享的类共享在程序之间,使不同的 Java 进程之间共享这个类来减少这个类占用的空间以及加载速度。不过 Java 10 中使用这个功能的步骤比较繁琐。
而 Java 13 中的 AppCDS,允许 Java 应用在程序执行结束时(如果 JVM 没有崩溃)进行动态存档;存储的内容包括所有加载的应用类型类和使用的类库,这些存储的类库本来并不存在于默认的 CDS 存档中。使用这个功能非常简单,只需要在程序启动时增加启动参数 。
# ArchiveClassesAtExit,程序结束时动态存档
bin/java -XX:ArchiveClassesAtExit=hello.jsa -cp hello.jar Hello
# SharedArchiveFile,使用指定存档启动
bin/java -XX:SharedArchiveFile=hello.jsa -cp hello.jar Hello
ZGC,归还未使用的内存 (实验性)
在 Java 13 之前,ZGC 虽然在清理内存时导致的停顿时间非常少,但是即使内存已经长时间没有使用,ZGC 也不会将内存返还给操作系统,这对那些十分关注内存占用的应用程序非常不友好。
比如:
- 资源按使用量付费的云上容器环境。
- 应用虽然长时间闲置,但是占用了内存,导致运行的其他程序内存紧张。
而新增的这个功能,可以让 ZGC 归还长时间没有使用的内存给操作系统,这对某些用户来说十分友好。
重新实现 Socket API
java.net.Socket 和 java.net.ServerSocket 类早在 Java 1.0 时就已经引入了,它们的实现的 Java 代码和 C 语言代码的混合,维护和调试都十分不易;而且这个实现还存在并发问题,有时候排查起来也很困难。
因此,在 Java 13 中引入了新的实现方式,使用了新的实现 NioSocketImpl 来代替老旧的 PlainSocketImpl 实现。虽然功能相同,但是老的方式在当前以及未来几个版本内不会删除,用户随时可以通过 -Djdk.net.usePlainSocketImpl 参数切换回老的实现方式,以兼容意外情况。
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Test {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8000)){
boolean running = true;
while(running){
Socket clientSocket = serverSocket.accept();
//do something with clientSocket
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用 Java 13 运行,通过参数 -XX:+TraceClassLoading 追踪加载的类,日志中可以看到 NioSocketImpl。
➜ develop ./jdk-13.0.2.jdk/Contents/Home/bin/java -XX:+TraceClassLoading Test.java | grep SocketImpl
[0.699s][info ][class,load] java.net.SocketImpl source: jrt:/java.base
[0.699s][info ][class,load] java.net.SocketImpl$$Lambda$173/0x0000000800c37440 source: java.net.SocketImpl
[0.702s][info ][class,load] sun.net.PlatformSocketImpl source: jrt:/java.base
[0.702s][info ][class,load] sun.nio.ch.NioSocketImpl source: jrt:/java.base
[0.713s][info ][class,load] sun.nio.ch.NioSocketImpl$FileDescriptorCloser source: jrt:/java.base
但在 Java 12 并不是 NioSocketImpl
➜ develop ./jdk-12.0.2.jdk/Contents/Home/bin/java -XX:+TraceClassLoading Test.java | grep SocketImpl
[0.665s][info ][class,load] java.net.SocketImpl source: jrt:/java.base
[0.665s][info ][class,load] java.net.AbstractPlainSocketImpl source: jrt:/java.base
[0.665s][info ][class,load] java.net.PlainSocketImpl source: jrt:/java.base
[0.665s][info ][class,load] java.net.SocksSocketImpl source: jrt:/java.base
[0.666s][info ][class,load] java.net.AbstractPlainSocketImpl$1 source: jrt:/java.base
文本块 (预览)
在这之前,如果我们把一个 JSON 赋值给字符串:
String content = "{\n"
+ " \"upperSummary\": null,\n"
+ " \"sensitiveTypeList\": null,\n"
+ " \"gmtModified\": \"2022-08-23 10:50:09\",\n"
+ " \"lowerGraph\": null,\n"
+ " \"signature\": \"\",\n"
+ " \"appName\": \"xxx\",\n"
+ " \"lowerSummary\": null,\n"
+ " \"gmtCreate\": \"2022-08-23 10:50:09\",\n"
+ " \"type\": \"CALL\",\n"
+ " \"name\": \"xxxx\",\n"
+ " \"subType\": \"yyy\",\n"
+ " \"id\": 1,\n"
+ " \"projectId\": 1,\n"
+ " \"status\": 1\n"
+ "}";
终于不用写丑陋的长字符串了,从 Java 13 开始你可以使用文本块的方式定义字符串了。
String content2 = """
{
"upperSummary": null,
"sensitiveTypeList": null,
"gmtModified": "2022-08-23 10:50:09",
"lowerGraph": null,
"signature": "",
"appName": "xxx",
"lowerSummary": null,
"gmtCreate": "2022-08-23 10:50:09",
"type": "CALL",
"name": "xxxx",
"subType": "yyy",
"id": 1,
"projectId": 1,
"status": 1
}
""";
不过这是一个预览功能,如果你要是在 Java 13 中使用需要手动开启预览功能,这个功能在 Java 15 中正式发布。
Java 14 新特性
Java 14早在 2019 年 9 月就已经发布,虽然不是长久支持版本,但是也带来了不少新功能。此篇文章写一下部分Java 14的新特性。
Java 14全部的新特性,请看官网:JDK 14 发行说明
Java各个版本的文档入口:Java平台,标准版文档
Java各个版本下载:https://jdk.java.net/archive/
instanceof 类型判断(预览)
在 Java 14 之前,使用 instanceof 进行类型判断之后,需要进行对象类型转换后才能使用。
public class Java14BeaforInstanceof {
public static void main(String[] args) {
Object obj = new ArrayList<>();
if (obj instanceof ArrayList) {
ArrayList list = (ArrayList)obj;
list.add("www.baidu.com");
}
System.out.println(obj);
}
}
而在 Java 14 中,可以在判断类型时指定变量名称进行类型转换,方便了使用。
public class Java14Instanceof {
public static void main(String[] args) {
Object obj = new ArrayList<>();
if (obj instanceof ArrayList list) {
list.add("www.baidu.com");
}
System.out.println(obj);
}
}
在使用 instanceof 判断类型成立后,会自动强制转换类型为指定类型。
打包工具(孵化)
在 Java 14 中,引入了打包工具,命令是 jpackage,使用 jpackage 命令可以把 JAR 包打包成不同操作系统支持的软件格式。
jpackage --name myapp --input lib --main-jar main.jar --main-class myapp.Main
常见平台格式如下:
- Linux: deb and rpm
- macOS: pkg and dmg
- Windows: msi and exe
要注意的是,jpackage 不支持交叉编译,也就是说在 windows 平台上是不能打包成 macOS 或者 Linux 系统的软件格式的。
G1 支持 NUMA(非统一内存访问)
G1 收集器现在可以感知 NUMA 内存分配方式,以提高 G1 的性能,可以使用 +XX:+UseNUMA 启用这项功能。
更多阅读文档:https://openjdk.java.net/jeps/345
更有用的 NullPointerExceptions
NullPointerException 一直都是一个比较常见的异常,但是在 Java 14 之前,如果一行有多个表达式时,这时报了空指针后,单纯的从报错信息来看,可能并不知道是哪个对象为 NULL 。
public class Java14NullPointerExceptions {
public static void main(String[] args) {
String content1 = "www.baidu.com";
String content2 = null;
int length = content1.length() + content2.length();
System.out.println(length);
}
}
在 Java 14 之前,从下面的报错中我们只能得到错误出现的行数,但是并不能确定是 conteng1 还是 content2 为 null。
java.lang.NullPointerException
at other.Other.java14NullPointerExceptions(Other.java:88)
但是在 Java 14 中,会清晰的告诉你 because “content2” is null 。
Records (预览)
record 是一种全新的类型,它本质上是一个 final 类,同时所有的属性都是 final 修饰,它会自动编译出 public get hashcode 、equals、toString 等方法,减少了代码编写量。
示例:编写一个 Dog record 类,定义 name 和 age 属性。
public record Dog(String name, Integer age) {
}
使用
Dog dog1 = new Dog("牧羊犬", 1);
Dog dog2 = new Dog("田园犬", 2);
Dog dog3 = new Dog("哈士奇", 3);
System.out.println(dog1);
System.out.println(dog2);
System.out.println(dog3);
输出:
Dog[name=牧羊犬, age=1]
Dog[name=田园犬, age=2]
Dog[name=哈士奇, age=3]
这个功能在 Java 15 中进行二次预览,在 Java 16 中正式发布。
Switch 表达式 (标准)
Switch 表达式改进从 Java 12 就已经开始了,Java 12 让 switch 支持了 case L-> 语法,Java 13 引入了 yield 关键词用于返回结果,但是在 Java 12 和 13 中功能都是预览版的,而在 Java 14 中,正式转正。
// 通过传入月份,输出月份所属的季节
public static String switchJava12(String month) {
return switch (month) {
case "march", "april", "may" -> "春天";
case "june", "july", "august" -> "夏天";
case "september", "october", "november" -> "秋天";
case "december", "january", "february" -> "冬天";
default -> "month erro";
};
}
// 通过传入月份,输出月份所属的季节
public static String switchJava13(String month) {
return switch (month) {
case "march", "april", "may":
yield "春天";
case "june", "july", "august":
yield "夏天";
case "september", "october", "november":
yield "秋天";
case "december", "january", "february":
yield "冬天";
default:
yield "month error";
};
}
文本块(二次预览)
文本块是 Java 13 引入的语法,在 Java 14 中对其进行了增强。文本块依旧是预览功能,这次更新增加了两个转义符。
- \ 结尾不换行
- \s 表示一个空格
String content = """
{
"upperSummary": null,\
"sensitiveTypeList": null,
"gmtModified": "2022-08-23\s10:50:09",
}
""";
System.out.println(content);
输出:
{
"upperSummary": null, "sensitiveTypeList": null,
"gmtModified": "2022-08-23 10:50:09",
}
文本块功能在 Java 15 中正式发布。
其他更新
- JEP 362:废弃对 Solaris 和 SPARC 端口支持从 Java 14 开始,放弃对 Solaris/SPARC, Solaris/x64, 和 Linux/SPARC 端口的支持,放弃一部分开发这势必会加快 Java 整体的开发节奏。
- JEP 363: 移除 CMS 垃圾收集器
移除对 CMS(Concurrent Mark Sweep) 垃圾收集器的支持,其实早在 Java 9 就开始移除 CMS 垃圾收集器了,只不过在 Java 14 中被正式删除。
- JEP 364:macOS 上的 ZGC(实验性)
Java 11 在 Linux 上引入了 Z 垃圾收集器 (ZGC),现在它可以移植到 macOS。
- JEP 365:Windows 上的 ZGC(实验性)
Java 11 在 Linux 上引入了 Z 垃圾收集器 (ZGC),现在它可以移植到 Windows 上(版本大于 1803)。
- JEP 366:弃用 ParallelScavenge + SerialOld GC 组合
由于使用场景不多,维护工作太大,废弃之。相关阅读:https://openjdk.java.net/jeps/366
- JEP 367:删除 Pack200 工具和 API
Java 15 新特性
Java 15 在 2020 年 9 月发布。
Java 15 全部的新特性,请看官网:JDK 15 发行说明
Java各个版本的文档入口:Java平台,标准版文档
Java各个版本下载:https://jdk.java.net/archive/
爱德华曲线算法(EdDSA)
Java 15 中增加了一个新的密码学算法,爱德华曲线算法(EdDSA)签名算法。它是由 Schnorr 算法发展而来,在 RFC8032 中被定义实现。
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Base64;
public class JEP339 {
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519");
KeyPair kp = kpg.generateKeyPair();
byte[] msg = "www.baidu.com".getBytes(StandardCharsets.UTF_8);
Signature sig = Signature.getInstance("Ed25519");
sig.initSign(kp.getPrivate());
sig.update(msg);
byte[] s = sig.sign();
System.out.println(Base64.getEncoder().encodeToString(s));
}
}
输出结果
VXlpxapU+LSWjVQ0QNJvdpUh6VI6PjSwOQ2pHu65bCfnLR13OyWKunlc9rc+7SMxCh2Mnqf7TmC/iOG8oimbAw==
Sealed Classes(密封类)预览
在 Java 中如果想让一个类不能被继承和修改,这时我们应该使用 final 关键字对类进行修饰。不过这种要么可以继承,要么不能继承的机制不够灵活,有些时候我们可能想让某个类可以被某些类型继承,但是又不能随意继承,是做不到的。Java 15 尝试解决这个问题,引入了 **sealed 类,被 sealed 修饰的类可以指定子类。这样这个类就只能被指定的类继承。
而且 sealed 修饰的类的机制具有传递性,它的子类必须使用指定的关键字进行修饰,且只能是 final、sealed、non-sealed **三者之一。
示例:犬类(Dog)只能被牧羊犬(Collie)和田园犬(TuGou)继承,使用 sealed 关键字。
public sealed interface Dog permits Collie, TuGou {
//...
}
牧羊犬(Collie)只能被边境牧羊犬(BorderCollie)继承。
public sealed class Collie implements Dog permits BorderCollie {
}
边境牧羊犬(BorderCollie)不能被继承,使用 final 关键字。
public final class BorderCollie extends Collie{
}
田园犬(ToGou)可以被任意继承,使用 non-sealed 关键字。
public non-sealed class TuGou implements Dog {
}
Hidden Classes(隐藏类)
这个特性让开发者可以引入一个无法被其他地方发现使用,且类的生命周期有限的类。这对运行时动态生成类的使用方式十分有利,可以减少内存占用,下面是一个使用示例。
public class JEP371Test {
public static String lookup() {
return "www.baidu.com";
}
}
把类 JEP371Test 编译后的 Class 转换成 Base64,然后使用 Java 15 新特性加载调用类中的 lookup 方法。
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Base64;
public class JEP371 {
private static String CLASS_INFO = "yv66vgAAADQAFAoAAgADBwAEDAAFAAYBABBqYXZhL2xhbmcvT2JqZWN0AQAGPGluaXQ+AQADKClWCAAIAQAOd3d3LndkYnl0ZS5jb20HAAoBABVjb20vd2RieXRlL0pFUDM3MVRlc3QBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAF0xjb20vd2RieXRlL0pFUDM3MVRlc3Q7AQAGbG9va3VwAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAApTb3VyY2VGaWxlAQAPSkVQMzcxVGVzdC5qYXZhACEACQACAAAAAAACAAEABQAGAAEACwAAAC8AAQABAAAABSq3AAGxAAAAAgAMAAAABgABAAAAAwANAAAADAABAAAABQAOAA8AAAAJABAAEQABAAsAAAAbAAEAAAAAAAMSB7AAAAABAAwAAAAGAAEAAAAEAAEAEgAAAAIAEw==";
public static void main(String[] args) throws Throwable {
byte[] classInBytes = Base64.getDecoder().decode(CLASS_INFO);
Class<?> proxy = MethodHandles.lookup()
.defineHiddenClass(classInBytes, true, MethodHandles.Lookup.ClassOption.NESTMATE)
.lookupClass();
System.out.println(proxy.getName());
MethodHandle mh = MethodHandles.lookup().findStatic(proxy, "lookup", MethodType.methodType(String.class));
String result = (String) mh.invokeExact();
System.out.println(result);
}
}
输出:
com.baidu.JEP371Test/0x0000000800c01800
www.baidu.com
移除 Nashorn JavaScript 引擎
Nashorn JavaScript 引擎在 Java 8 中被引入,在 Java 11 中被标记为废弃。由于 ECMAScript 语言发展很快,维护 Nashorn JavaScript 的成本过于高昂,在 Java 15 中被彻底删除。
阅读:Nashorn JavaScript Engine (opens new window),Deprecate the Nashorn JavaScript Engine(opens new window)
重新实现 DatagramSocket API
Java 13 中重新实现了旧的 Socket API,在介绍 Java 13 时还有一部分做了这方面的介绍。
现在,Java 15 重新实现了遗留的 DatagramSocket
禁用和废弃偏向锁(Biased Locking)
在之前,JVM 在处理同步操作,如使用 synchronized 同步时,有一套锁的升级机制,其中有一个锁机制就是偏向锁。然而通过目前的 Java 开发环境来看,使用这些被 synchronized 同步的类的机会并不多,如开发者更喜欢使用 **HashMap **或者 **ArrayList **而非 HashTable 和 Vector。
即使换个角度,当初使用偏向锁是为了提高性能,如今看来性能提升的程度和使用次数都不太有用。而偏向锁的引入增加了 JVM 的复杂性。
所以现在偏向锁被默认禁用,在不久的将来将会彻底删除,对于 Java 15,我们仍然可以使用 -XX:+UseBiasedLocking 启用偏向锁定,但它会提示 这是一个已弃用的 API。
instanceof 类型匹配 (二次预览)
instanceof 类型匹配在 Java 14 中已经改进,这次仅仅再次预览,没有任何改动,用于接受更多的使用反馈。这个特性在 Java 16 中成为正式特性。
在之前,使用 instanceof 进行类型判断之后,需要进行对象类型转换后才能使用。
ZGC: 可扩展低延迟垃圾收集器(正式发布)
ZGC 垃圾收集器在 Java 11 中被引入,但是因为收集器的复杂性,当初决定逐渐引入。然后不断的听取用户的反馈建议修复问题。而现在,已经很久没有收到用户的问题反馈了,ZGC 是时候投入正式使用阶段了。所以在 Java 15 中 ZGC 正式发布,可以使用下面的参数启用 ZGC。
java -XX:+UseZGC className
文本块
文本块在 Java 12 JEP 326 原始字符串文字 (opens new window)中引入,
在 Java 13 JEP 355:文本块(预览) (opens new window)中开始预览,
在 Java 14 JEP 368:文本块(第二次预览) (opens new window),
而现在,在 Java 15 ,文本块是正式的功能特性了。
String content = """
{
"upperSummary": null,\
"sensitiveTypeList": null,
"gmtModified": "2022-08-23\s10:50:09",
}
""";
System.out.println(content);
Shenandoah: 低停顿时间的垃圾收集器
Shenandoah 垃圾收集器在 Java 12 中开始引入,Java 15 中成为了正式功能的一部分,可以使用下面的参数启用 Shenandoah 垃圾收集器。
java -XX:+UseShenandoahGC
但是 openJDK 15 中默认是没有 Shenandoah 收集器,想要使用此功能可以下载 AdoptOpenJDK (opens new window)。
为什么 openJDK 中没有 Shenandoah 垃圾收集器?
Shenandoah 是一个高性能、低暂停时间的垃圾收集器,它是 Red Hat 主导的项目。当 Red Hat 第一次提议将 Shenandoah 贡献给 OpenJDK 时,Oracle 明确表示不想支持它,OpenJDK 作为一个免费软件,不想支持 Red Hat 的 Shenandoah 完全没有问题。
最后 Red Hat 选择和 Oracle 合作设计一个真正干净的可插拔垃圾收集器接口,允许任何人轻松选择垃圾收集器以包含在他们的构建中。最终 Shenandoah 进入了 JDK 12,但是没有构建进 OpenJDK。
Records(二次预览)
在 Java 14 中引入了 Record 类,Java 15 中对 Record 进行了增强。使它可以支持密封类型、Record 注解以及相关的反射 API 等。
示例:Record 支持密封(sealed)类型。
public sealed interface DataBase permits DataBaseSelect, DataBaseUpdate {
}
final record DataBaseSelect(@Deprecated String table, String sql) implements DataBase {
}
final record DataBaseUpdate() implements DataBase {
}
在 java.lang.Class 增加了两个公共方法用于获取 Record 类信息:
- RecordComponent[] getRecordComponents()
- boolean isRecord()
其他更新
- JEP 381:删除 Solaris 和 SPARC 端口
Java 14 JEP 362 (opens new window)弃用了 Solaris/SPARC、Solaris/x64 和 Linux/SPARC 端口,现在它在 Java 15 中被正式删除。
- JEP 383:外部内存访问 API(第二个孵化器)
- JEP 385:废弃 RMI 激活机制
只是废弃 RMI 激活机制,不影响 RMI 其他功能。
Java 16 新特性
Java 16 在 2021 年 3 月 16 日正式发布,在语法上的新特性更新比较多,主要是一些之前版本引入的预览特性正式发布。比如Java14中的打包工具、instanceof、Record类。
Java 16 全部的新特性,请看官网:JDK 16 发行说明
Java各个版本的文档入口:Java平台,标准版文档
Java各个版本下载:https://jdk.java.net/archive/
instanceof 模式匹配
改进 instanceof 在 Java 14 中已经提出,在 Java 15 中继续预览,而现在,在 Java 16 中成为正式功能。
在之前,使用 instanceof 需要如下操作:
if (obj instanceof String) {
String s = (String) obj; // grr...
...
}
多余的类型强制转换,而现在
if (obj instanceof String s) {
// Let pattern matching do the work!
...
}
Records
Record 成为 Java 16 的正式功能,下面是介绍 Java 14 时关于 Record 的介绍。
record 是一种全新的类型,它本质上是一个 final 类,同时所有的属性都是 final 修饰,它会自动编译出 public get hashcode 、equals、toString 等方法,减少了代码编写量。
Dog dog1 = new Dog("牧羊犬", 1);
Dog dog2 = new Dog("田园犬", 2);
Dog dog3 = new Dog("哈士奇", 3);
System.out.println(dog1);
System.out.println(dog2);
System.out.println(dog3);
Sealed Classes(密封类)预览
Sealed Classes 再次预览,在 Java 15 新特性介绍文章里已经介绍过相关功能,并且给出了详细的使用演示,这里不再重复介绍。
下面是一段引用:
我们都知道,在 Java 中如果想让一个类不能被继承和修改,这时我们应该使用 final 关键字对类进行修饰。不过这种要么可以继承,要么不能继承的机制不够灵活,有些时候我们可能想让某个类可以被某些类型继承,但是又不能随意继承,是做不到的。Java 15 尝试解决这个问题,引入了 sealed 类,被 sealed 修饰的类可以指定子类。这样这个类就只能被指定的类继承。
而且 sealed 修饰的类的机制具有传递性,它的子类必须使用指定的关键字进行修饰,且只能是 final、sealed、non-sealed 三者之一。
Java 17 新特性
Java 17 在 2021 年 9 月 14 日正式发布,Java 17 是一个长期支持(LTS)版本。来看几个重要的。
Java 17 全部的新特性,请看官网:JDK 17 发行说明
Java各个版本的文档入口:Java平台,标准版文档
Java各个版本下载:https://jdk.java.net/archive/
恢复严格的浮点语义
既然是恢复严格的浮点语义,那么说明在某个时间点之前,是始终严格的浮点语义的。其实在 Java SE 1.2 之前,所有的浮点计算都是严格的,但是以当初的情况来看,过于严格的浮点计算在当初流行的 x86 架构和 x87 浮点协议处理器上运行,需要大量的额外的指令开销,所以在 Java SE 1.2 开始,需要手动使用关键字 strictfp(strict float point) 才能启用严格的浮点计算。
但是在 2021 年的今天,硬件早已发生巨变,当初的问题已经不存在了,所以从 Java 17 开始,恢复了始终严格的浮点语义这一特性。
扩展:strictfp 是 Java 中的一个关键字,大多数人可能没有注意过它,它可以用在类、接口或者方法上,被 strictfp 修饰的部分中的 float 和 double 表达式会进行严格浮点计算。
下面是一个示例,其中的 testStrictfp() 被 strictfp 修饰。
public static void main(String[] args) {
testStrictfp();
}
public strictfp static void testStrictfp() {
float aFloat = 0.6666666666666666666f;
double aDouble = 0.88888888888888888d;
double sum = aFloat + aDouble;
System.out.println("sum: " + sum);
}
增强的伪随机数生成器
为伪随机数生成器 RPNG(pseudorandom number generator)增加了新的接口类型和实现,让在代码中使用各种 PRNG 算法变得容易许多。
这次增加了 **RandomGenerator **接口,为所有的 PRNG 算法提供统一的 API,并且可以获取不同类型的 PRNG 对象流。同时也提供了一个新类 **RandomGeneratorFactory **用于构造各种 **RandomGenerator **实例,在 **RandomGeneratorFactory **中使用 **ServiceLoader.provider **来加载各种 PRNG 实现。
下面是一个使用示例:随便选择一个 PRNG 算法生成 5 个 10 以内的随机数。
import java.util.Date;
import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;
import java.util.stream.Stream;
public class JEP356 {
public static void main(String[] args) {
RandomGeneratorFactory<RandomGenerator> l128X256MixRandom = RandomGeneratorFactory.of("L128X256MixRandom");
// 使用时间戳作为随机数种子
RandomGenerator randomGenerator = l128X256MixRandom.create(System.currentTimeMillis());
for (int i = 0; i < 5; i++) {
System.out.println(randomGenerator.nextInt(10));
}
}
}
输出:
7
3
4
4
6
你也可以遍历出所有的 PRNG 算法。
RandomGeneratorFactory.all().forEach(factory -> {
System.out.println(factory.group() + ":" + factory.name());
});
输出:
LXM:L32X64MixRandom
LXM:L128X128MixRandom
LXM:L64X128MixRandom
Legacy:SecureRandom
LXM:L128X1024MixRandom
LXM:L64X128StarStarRandom
Xoshiro:Xoshiro256PlusPlus
LXM:L64X256MixRandom
Legacy:Random
Xoroshiro:Xoroshiro128PlusPlus
LXM:L128X256MixRandom
Legacy:SplittableRandom
LXM:L64X1024MixRandom
可以看到 Legacy:Random 也在其中,新的 API 兼容了老的 Random 方式,所以你也可以使用新的 API 调用 Random 类生成随机数。
// 使用 Random
RandomGeneratorFactory<RandomGenerator> l128X256MixRandom = RandomGeneratorFactory.of("Random");
// 使用时间戳作为随机数种子
RandomGenerator randomGenerator = l128X256MixRandom.create(System.currentTimeMillis());
for (int i = 0; i < 5; i++) {
System.out.println(randomGenerator.nextInt(10));
}
使用新的 macOS 渲染库
macOS 为了提高图形的渲染性能,在 2018 年 9 月抛弃了之前的 OpenGL 渲染库 ,而使用了 Apple Metal 进行代替。Java 17 这次更新开始支持 Apple Metal,不过对于 API 没有任何改变,这一些都是内部修改。
支持 macOS/AArch64 架构
起因是 Apple 在 2020 年 6 月的 WWDC 演讲中宣布,将开启一项长期的将 Macintosh 计算机系列从 x64 过度到 AArch64 的长期计划,因此需要尽快的让 JDK 支持 macOS/AArch64 。
Linux 上的 AArch64 支持以及在 Java 16 时已经支持。
删除已弃用的 Applet API
Applet 是使用 Java 编写的可以嵌入到 HTML 中的小应用程序,嵌入方式是通过普通的 HTML 标记语法,由于早已过时,几乎没有场景在使用了。
<applet code="Hello.class" height=200 width=200></applet>
Applet API 在 Java 9 时已经标记了废弃,现在 Java 17 中将彻底删除。
更强的 JDK 内部封装
如 Java 16 的 JEP 396 中描述的一样,为了提高 JDK 的安全性,使 --illegal-access 选项的默认模式从允许更改为拒绝。通过此更改,JDK 的内部包和 API(关键内部 API (opens new window)除外)将不再默认打开。
但是在 Java 17 中,除了 sun.misc.Unsafe ,使用 --illegal-access 命令也不能打开 JDK 内部的强封装模式了,除了 sun.misc.Unsafe API 。
在 Java 17 中使用 --illegal-access 选项将会得到一个命令已经移除的警告。
➜ bin ./java -version
openjdk version "17" 2021-09-14
OpenJDK Runtime Environment (build 17+35-2724)
OpenJDK 64-Bit Server VM (build 17+35-2724, mixed mode, sharing)
➜ bin ./java --illegal-access=warn
OpenJDK 64-Bit Server VM warning: Ignoring option --illegal-access=warn; support was removed in 17.0
switch 的类型匹配(预览)
如 instanceof 一样,为 switch 也增加了类型匹配自动转换功能。
在之前,使用 instanceof 需要如下操作:
if (obj instanceof String) {
String s = (String) obj; // grr...
...
}
多余的类型强制转换,而现在:
if (obj instanceof String s) {
// Let pattern matching do the work!
...
}
也可以使用如下的方式:
static String formatterPatternSwitch(Object o) {
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
}
对null也有新的方式:
// Java 17 之前
static void testFooBar(String s) {
if (s == null) {
System.out.println("oops!");
return;
}
switch (s) {
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}
// Java 17
static void testFooBar(String s) {
switch (s) {
case null -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}
移除 RMI Activation
移除了在 JEP 385 中被标记废除的 RMI(Remote Method Invocation)Activation,但是 RMI 其他部分不会受影响。
RMI Activation 在 Java 15 中的 JEP 385 已经被标记为过时废弃,至今没有收到不良反馈,因此决定在 Java 17 中正式移除。
密封类(Sealed Classes)
Sealed Classes 在 Java 15 中的 JEP 360 中提出,在 Java 16 中的 JEP 397 再次预览,现在 Java 17 中成为正式的功能,相比 Java 16 并没有功能变化,这里不再重复介绍,想了解的可以参考之前章节。
移除实验性的 AOT 和 JIT 编译器
在 Java 9 的 JEP 295 中,引入了实验性的提前编译 jaotc 工具,但是这个特性自从引入依赖用处都不太大,而且需要大量的维护工作,所以在 Java 17 中决定删除这个特性。
主要移除了三个 JDK 模块:
- jdk.aot - jaotc 工具。
- Jdk.internal.vm.compiler - Graal 编译器。
- jdk.internal.vm.compiler.management
同时也移除了部分与 AOT 编译相关的 HotSpot 代码:
- src/hotspot/share/aot — dumps and loads AOT code
- Additional code guarded by #if INCLUDE_AOT
弃用 Security Manager
Security Manager 在 JDK 1.0 时就已经引入,但是它一直都不是保护服务端以及客户端 Java 代码的主要手段,为了 Java 的继续发展,决定弃用 Security Manager,在不久的未来进行删除。
@Deprecated(since="17", forRemoval=true)
public class SecurityManager {
// ...
}
外部函数和内存 API (孵化)
新的 API 允许 Java 开发者与 JVM 之外的代码和数据进行交互,通过调用外部函数,可以在不使用 JNI 的情况下调用本地库。
这是一个孵化功能;需要添加 --add-modules jdk.incubator.foreign 来编译和运行 Java 代码。
- Java 14 JEP 370 (opens new window)引入了外部内存访问 API(孵化器)
- Java 15 JEP 383 (opens new window)引入了外部内存访问 API(第二孵化器)
- Java 16 JEP 389 (opens new window)引入了外部链接器 API(孵化器)
- Java 16 JEP 393 (opens new window)引入了外部内存访问 API(第三孵化器)
- Java 17 JEP 412 (opens new window)引入了外部函数和内存 API(孵化器)
Vector API(二次孵化)
在 Java 16 中引入一个新的 API 来进行向量计算,它可以在运行时可靠的编译为支持的 CPU 架构,从而实现更优的计算能力。
现在 Java 17 中改进了 Vector API 性能,增强了例如对字符的操作、字节向量与布尔数组之间的相互转换等功能。
指定上下文的反序列化过滤器
Java 中的序列化一直都是非常重要的功能,如果没有序列化功能,Java 可能都不会占据开发语言的主导地位,序列化让远程处理变得容易和透明,同时也促进了 Java EE 的成功。
但是 Java 序列化的问题也很多,它几乎会犯下所有的可以想象的错误,为开发者带来持续的维护工作。但是要说明的是序列化的概念是没有错的,把对象转换为可以在 JVM 之间自由传输,并且可以在另一端重新构建的能力是完全合理的想法,问题在于 Java 中的序列化设计存在风险,以至于爆出过很多和序列化相关的漏洞。
反序列化危险的一个原因是,有时候我们不好验证将要进行反序列化的内容是否存在风险,而传入的数据流可以自由引用对象,很有可能这个数据流就是攻击者精心构造的恶意代码。
所以,JEP 415 允许在反序列化时,通过一个过滤配置,来告知本次反序列化允许或者禁止操作的类,反序列化时碰到被禁止的类,则会反序列化失败。
Java 18 新特性
Java 18 在 2022 年 3 月 22 日正式发布,Java 18 不是一个长期支持版本,这次更新共带来 9 个新功能。
Java 18 全部的新特性,请看官网:JDK 18 发行说明
Java各个版本的文档入口:Java平台,标准版文档
Java各个版本下载:https://jdk.java.net/archive/
默认UTF-8字符编码
JDK 一直都是支持 UTF-8 字符编码,这次是把 UTF-8 设置为了默认编码,也就是在不加任何指定的情况下,默认所有需要用到编码的 JDK API都使用 UTF-8 编码,这样就可以避免因为不同系统,不同地区,不同环境之间产生的编码问题。
使用下面的命令可以输出 JDK 的当前编码。
# Mac 系统,默认 UTF-8
➜ ~ java -XshowSettings:properties -version 2>&1 | grep file.encoding
file.encoding = UTF-8
file.encoding.pkg = sun.io
➜ ~
简单的 Web 服务器
在 Java 18 中,提供了一个新命令 jwebserver,运行这个命令可以启动一个简单的 、最小化的静态 Web 服务器,它不支持 CGI 和 Servlet,所以最好的使用场景是用来测试、教育、演示等需求。
其实在如 Python、Ruby、PHP、Erlang 等许多平台都提供了开箱即用的 Web 服务器,可见一个简单的 Web 服务器是一个常见的需求,Java 一直没有这方面的支持,现在可以了。
在 Java 18 中,使用 jwebserver 启动一个 Web 服务器,默认发布的是当前目录。
在当前目录创建一个网页文件 index.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h1>CSDN-阿提说说</h1>
</body>
</html>
启动 jwebserver
Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving D:\javaprojects\java18-example\src\test\java\other and subdirectories on
127.0.0.1 port 8000
URL http://127.0.0.1:8000/
通过 help 参数可以查看 jwebserver 支持的参数。
➜ bin ./jwebserver --help
Usage: jwebserver [-b bind address] [-p port] [-d directory]
[-o none|info|verbose] [-h to show options]
[-version to show version information]
Options:
-b, --bind-address - 绑定地址. Default: 127.0.0.1 (loopback).
For all interfaces use "-b 0.0.0.0" or "-b ::".
-d, --directory - 指定目录. Default: current directory.
-o, --output - Output format. none|info|verbose. Default: info.
-p, --port - 绑定端口. Default: 8000.
-h, -?, --help - Prints this help message and exits.
-version, --version - Prints version information and exits.
To stop the server, press Ctrl + C.
Javadoc 中支持代码片段
在 Java 18 之前,已经支持在 Javadoc 中引入代码片段,这样可以在某些场景下更好的展示描述信息,但是之前的支持功能有限,比如我想高亮代码片段中的某一段代码是无能为力的。现在 Java 18 优化了这个问题,增加了 @snippet 来引入更高级的代码片段。
在 Java 18 之前,使用
{@code …}
来引入代码片段。
/\*\*
\* 在 Java 18 之后可以使用新的方式
\* 下面的代码演示如何使用 {@code Optional.isPresent}:
\* {@snippet :
\* if (v.isPresent()) {
\* System.out.println("v: " + v.get());
\* }
![img](https://img-blog.csdnimg.cn/img_convert/d4c0831878cb957769a1e8caeee416e7.png)
![img](https://img-blog.csdnimg.cn/img_convert/d4e498e95efe0f1116626073177d5ed3.png)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
8 编码,这样就可以避免因为不同系统,不同地区,不同环境之间产生的编码问题。
使用下面的命令可以输出 JDK 的当前编码。
Mac 系统,默认 UTF-8
➜ ~ java -XshowSettings:properties -version 2>&1 | grep file.encoding
file.encoding = UTF-8
file.encoding.pkg = sun.io
➜ ~
### 简单的 Web 服务器
在 Java 18 中,提供了一个新命令 jwebserver,运行这个命令可以启动一个简单的 、最小化的静态 Web 服务器,它不支持 CGI 和 Servlet,所以最好的使用场景是用来测试、教育、演示等需求。
其实在如 Python、Ruby、PHP、Erlang 等许多平台都提供了开箱即用的 Web 服务器,可见一个简单的 Web 服务器是一个常见的需求,Java 一直没有这方面的支持,现在可以了。
在 Java 18 中,使用 jwebserver 启动一个 Web 服务器,默认发布的是当前目录。
在当前目录创建一个网页文件 index.html
CSDN-阿提说说
启动 jwebserver
Binding to loopback by default. For all interfaces use “-b 0.0.0.0” or “-b ::”.
Serving D:\javaprojects\java18-example\src\test\java\other and subdirectories on
127.0.0.1 port 8000
URL http://127.0.0.1:8000/
通过 help 参数可以查看 jwebserver 支持的参数。
➜ bin ./jwebserver --help
Usage: jwebserver [-b bind address] [-p port] [-d directory]
[-o none|info|verbose] [-h to show options]
[-version to show version information]
Options:
-b, --bind-address - 绑定地址. Default: 127.0.0.1 (loopback).
For all interfaces use “-b 0.0.0.0” or “-b ::”.
-d, --directory - 指定目录. Default: current directory.
-o, --output - Output format. none|info|verbose. Default: info.
-p, --port - 绑定端口. Default: 8000.
-h, -?, --help - Prints this help message and exits.
-version, --version - Prints version information and exits.
To stop the server, press Ctrl + C.
### Javadoc 中支持代码片段
在 Java 18 之前,已经支持在 Javadoc 中引入代码片段,这样可以在某些场景下更好的展示描述信息,但是之前的支持功能有限,比如我想高亮代码片段中的某一段代码是无能为力的。现在 Java 18 优化了这个问题,增加了 **@snippet** 来引入更高级的代码片段。
在 Java 18 之前,使用
{@code …}
来引入代码片段。
/**
* 在 Java 18 之后可以使用新的方式
* 下面的代码演示如何使用 {@code Optional.isPresent}:
* {@snippet :
* if (v.isPresent()) {
* System.out.println("v: " + v.get());
* }
[外链图片转存中…(img-NK7oAWQ5-1715777715653)]
[外链图片转存中…(img-DnB11Urn-1715777715654)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!