Java 12~16 新特性
Java 12新特性(2019年3月)
正式特性
微基准测试套件
Java 12 引入了 JMH(Java Microbenchmark Harness)作为官方的微基准测试工具:
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class StringConcatBenchmark {
@Benchmark
public String stringConcat() {
return "Hello" + " " + "World";
}
@Benchmark
public String stringBuilder() {
return new StringBuilder()
.append("Hello")
.append(" ")
.append("World")
.toString();
}
}JMH 可以帮助开发者准确测量代码性能,避免 JT 编译器优化带来的干扰。
默认 CDS 归档
Java 12 默认启用类数据共享(CDS)归档,JDK 安装时会预先创建一个包含常用核心类的归档文件
# 查看默认 CDS 归档
java -Xshare:dump
# 使用 CDS 归档启动(默认启用)
java -Xshare:auto MyApp这可以减少应用启动时间和内存占用。
G1 垃圾收集器改进
Java 12 对 G1 垃圾收集器进行了两项重要改进:
- 可中止混合收集:G1 可以在混合收集过程中中止,以满足暂停时间目标
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 MyApp- 及时返回未使用内存:G1 可以在空闲时将未使用的堆内存返回给操作系统
java -XX:+UseG1GC -XX:G1PeriodicGCInterval=30000 MyAppString API增强
String text = "Hello World";
// indent() - 调整缩进
String indented = text.indent(4);
System.out.println(indented); // " Hello World"
// transform() - 函数式转换
String result = text.transform(s -> s.toUpperCase())
.transform(s -> s.replace(" ", "_"));
System.out.println(result); // "HELLO_WORLD"CompactNumberFormat
Java 12 引入了紧凑数字格式化:
import java.text.CompactNumberFormat;
import java.text.NumberFormat;
import java.util.Locale;
// 创建紧凑数字格式化器
NumberFormat cnf = CompactNumberFormat.getInstance(
Locale.CHINA, NumberFormat.Style.SHORT);
// 格式化大数字
System.out.println(cnf.format(1000)); // 1K
System.out.println(cnf.format(1000000)); // 100万
System.out.println(cnf.format(1000000000)); // 10亿
// 长格式
NumberFormat longFormat = CompactNumberFormat.getInstance(
Locale.CHINA, NumberFormat.Style.LONG);
System.out.println(longFormat.format(1000000)); // 100万实验特性
Shenandoah 垃圾收集器
Java 12 引入了 Shenandoah Gc 作为实验特性,这是一个低延迟垃圾收集器
# 启用 Shenandoah GC
java -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC MyAppShenandoah 的特点:
- 低延迟:暂停时间与堆大小无关
- 并发:大部分工作与应用线程并发执行。
- 适用性:适合对延迟敏感的应用
预览特性
升级的switch语句
在jdk12之前的switch语句中,如果没有写break,则会出现case穿透现象,下面是对case穿透的一个应用,根据输入的月份打印相应的季节。
int month = 3;
switch (month) {
case 3:
case 4:
case 5:
System.out.println("spring");
break;
case 6:
case 7:
case 8:
System.out.println("summer");
break;
case 9:
case 10:
case 11:
System.out.println("autumn");
break;
case 12:
case 1:
case 2:
System.out.println("winter");
break;
default:
System.out.println("wrong");
break;
}在jdk12之后可以省略全部的break和部分case,这样使用
int month = 3;
switch (month) {
case 3,4,5 -> System.out.println("spring");
case 6,7,8 -> System.out.println("summer");
case 9,10,11 -> System.out.println("autumn");
case 12, 1,2 -> System.out.println("winter");
default -> System.out.println("wrong");
}主要改进:
- 箭头语法:使用 ->避免 fall-through 问题
- 多标签:case A,B,C->支持多个条件
- 表达式:可以直接赋值给变量
- 简洁性:减少样板代码
这个在Java12中是预览功能,如果需要编译和运行的话需要使用下面命令
编译:
javac --enable-preview -source 12 Test.Java
运行:
java --enable-preview TestJava 13新特性(2019年9月)
正式特性
动态 CDS 归档
Java 13 支持在应用运行时动态创建 CDS 归档:
# 运行时生成 CDS 归档
java -XX:ArchiveClassesAtExit=myapp.jsa MyApp
# 使用生成的归档启动
java -XX:SharedArchiveFile=myapp.jsa MyApp动态 CDS 的优势:
- 自动化:无需手动分析类加载。
- 精确性:只包含实际使用的类
- 便利性:适合复杂的应用场景
ZGC 内存管理改进
Java 13 改进了 ZGC的内存管理,支持取消提交未使用的内存
java -XX:+UnlockExperimentalVMOptions
-XX:+UseZGC
-XX:ZUncommitDelay=300
MyApp这个改进让 ZGC 可以
- 在内存压力小时释放内存
- 提高系统整体内存利用率
- 适应动态的内存需求
重新实现传统套接字 API
Java13重新实现了 java.net.Socket 和 java.net.ServerSocket APl
// API 使用方式不变,但底层实现更现代化
ServerSocket serverSocket = new ServerSocket(8080);
Socket clientSocket = serverSocket.accept();
// 新实现的优势:
// 1. 更好的可维护性
// 2. 为 Project Loom(虚拟线程)做准备
// 3. 更好的调试支持这个改进对开发者来说是透明的,但为未来的虚拟线程等特性奠定了基础。
String API增强
// stripIndent() - 去除公共缩进
String multiLine = """
Line 1
Line 2
Line 3
""";
String stripped = multiLine.stripIndent();
// translateEscapes() - 处理转义字符
String escaped = "Hello\\nWorld\\t!";
String translated = escaped.translateEscapes();
System.out.println(translated); // Hello
// World !Files APl 增强
Java 13 为 Files 类添加了mismatch()方法:
Path file1 = Paths.get("file1.txt");
Path file2 = Paths.get("file2.txt");
// 比较两个文件,返回第一个不同字节的位置
long mismatch = Files.mismatch(file1, file2);
if (mismatch == -1L) {
System.out.println("文件内容相同");
} else {
System.out.println("文件在位置 " + mismatch + " 开始不同");
}预览特性
升级的switch语句(第二次预览)
JDK13中对switch语句又进行了升级,主要引入了 yield 关键字
示例:
// Java 13 中的 switch 表达式
int score = switch (grade) {
case 'A', 'B' -> {
System.out.println("优秀成绩!");
yield 90; // 使用 yield 返回值
}
case 'C' -> 70;
case 'D' -> 60;
default -> {
System.out.println("需要努力!");
yield 0;
}
};yield 关键字用于在代码块中返回值,类似于return,但专用于 switch 表达式。
文本块的变化(预览)
在jdk13之前的版本中如果输入的字符串中有换行的话,需要添加换行符
String s = "Hello\nWorld\nLearn\nJava";
System.out.println(s);jdk13之后可以直接这样写:
String s = """
Hello
World
Learn
Java
""";
System.out.println(s);这样的字符串更加一目了然。
文本块的特点:
- 三引号语法:使用 """ 包围
- 保持格式:保留原有的缩进和换行
- 转义简化:内部的引号不需要转义
特别适合编写
- HTML/XML内容
- SQL查询语句
- JSON 字符串
- 正则表达式
Java 14新特性(2020年3月)
java 14 新增了很多特性,这里针对较为突出的特性进行说明。jdk12和jdk13中预览版的switch特性,在jdk14中已经是正式的语法了。
正式特性
Switch表达式改进
Java 14 将 Switch 表达式转正了,让条件判断变得更简洁和安全。在这之前,传统的 switch 语句存在不少问题,比如需要手动添加 break 防止穿透、赋值不方便等
String dayType;
switch (day) {
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
case FRIDAY:
dayType = "工作日";
break;
case SATURDAY:
case SUNDAY:
dayType = "周末";
break;
default:
dayType = "未知";
break;
}
// 赋值不方便
int score;
switch (grade) {
case 'A':
System.out.println("优秀!");
score = 90;
break;
case 'B':
score = 80;
break;
default:
score = 0;
}Java14后这么写
// Java 14 的简洁写法
String dayType = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "工作日";
case SATURDAY, SUNDAY -> "周末";
default -> "未知";
};
// 支持复杂逻辑的 yield 关键字
int score = switch (grade) {
case 'A' -> {
System.out.println("优秀!");
yield 90; // 使用 yield 返回值
}
case 'B' -> 80;
default -> 0;
};根据月份判断季节
// 实际应用示例:根据月份判断季节
String season = switch (month) {
case 12, 1, 2 -> "冬季";
case 3, 4, 5 -> "春季";
case 6, 7, 8 -> "夏季";
case 9, 10, 11 -> "秋季";
default -> throw new IllegalArgumentException("无效月份: " + month);
};友好的空指针(NullPointerException)提示
jdk14中添加了对于空指针异常友好的提示,便于开发者快速定位空指针的对象。示例代码:
class Machine{
public void start(){
System.out.println("启动");
}
}
class Engine{
public Machine machine;
}
class Car{
public Engine engine;
}
public class TestNull{
public static void main(String[] args){
//这里会报出空指针,但是哪个对象是null呢?
new Car().engine.machine.start();
}
}这个特性需要使用JVM参数启动
java -XX:+ShowCodeDetailsInExceptionMessages TestNull垃圾收集器改进
- G1 的 NUMA 感知内存分配
Java 14 改进了 G1 垃圾收集器,使其能够感知 NUMA(Non-Uniform Memory Access)架构
java -XX:+UseG1GC -XX:+UseNUMA MyApp这个改进可以
- 提高多 CPU 系统的内存访问效率
- 减少跨 NUMA 节点的内存访问
- 改善大型服务器的性能表现
- macOs 和 Windows 上的ZGC
Java 14 将 ZGC 扩展到 macOS 和 Windows 平台
# macOS 上启用 ZGC
java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC MyApp
# Windows 上启用 ZGC
java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC MyApp之前 ZGC 只支持 Linux,现在跨平台支持让更多开发者可以体验低延迟 GC。
JFR 事件流
Java 14 引入了 JFR(Java Flight Recorder)事件流 API,允许实时消费 JFR 事件
import jdk.jfr.consumer.RecordingStream;
public class JFRStreamExample {
public static void main(String[] args) {
try (RecordingStream rs = new RecordingStream()) {
// 启用 GC 事件
rs.enable("jdk.GarbageCollection");
// 设置事件处理器
rs.onEvent("jdk.GarbageCollection", event -> {
System.out.println("GC 事件: " + event.getValue("gcId"));
System.out.println("GC 时间: " + event.getDuration());
});
// 启用 CPU 负载事件
rs.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(1));
rs.onEvent("jdk.CPULoad", event -> {
double cpuLoad = event.getFloat("machineTotal");
System.out.println("CPU 负载: " + cpuLoad * 100 + "%");
});
// 开始流式处理
rs.start();
}
}
}非易失性映射字节缓冲区
Java 14 支持将 MappedByteBuffer 映射到非易失性内存(NVM)
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
// 映射到非易失性内存
try (FileChannel channel = FileChannel.open(
Paths.get("/mnt/pmem/datafile"),
StandardOpenOption.READ,
StandardOpenOption.WRITE)) {
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_WRITE, 0, channel.size());
// 数据直接持久化到 NVM
buffer.putInt(42);
buffer.force(); // 强制写入
}删除 CMS 垃圾收集器
Java 14 正式移除了 CMS(Concurrent MarkSweep)垃圾收集器
# 这个参数在 Java 14 中不再有效
# java -XX:+UseConcMarkSweepGC MyApp // 已移除
# 推荐使用 G1 或 ZGC
java -XX:+UseG1GC MyApp删除 Pack200 工具和 API
Java 14 移除了 Pack200 压缩工具和相关 API
// 这些 API 在 Java 14 中已被移除
// Pack200.Packer packer = Pack200.newPacker();
// Pack200.Unpacker unpacker = Pack200.newUnpacker();预览特性
instanceof模式匹配
该特性可以减少强制类型转换的操作,简化了代码,代码示例:
public class TestInstanceof{
public static void main(String[] args){
//jdk14之前的写法
Object obj = new Integer(1);
if(obj instanceof Integer){
Integer i = (Integer)obj;
int result = i + 10;
System.out.println(i);
}
//jdk14新特性 不用再强制转换了
//这里相当于是将obj强制为Integer之后赋值给i了
if(obj instanceof Integer i){
// 也就是可以直接用i了,省略了一步
int result = i + 10;
System.out.println(i);
}else{
//作用域问题,这里是无法访问i的
}
}
}record类型
之前在编写Java Bean类的时候,需要编写成员变量,get方法,构造方法,toString方法,hashcode方法,equals方法。这些方法通常会通过开发工具来生成,在jdk14中新增了record类型,通过该类型可以省去这些代码的编写。
jdk14编写User类:
public record User(String name,Integer age){}
//使用Record
public class TestUser{
public static void main(String[] args){
User u = new User("jack",15);
System.out.println(u);
System.out.println(u.name());
}
}通过反编译命令可以看到该字节码文件中的内容,User类是继承了Record类型:
javap -p -private user记录类型有自动生成的成员,包括:
- 状态描述中的每个组件都有对应的private final字段。
- 状态描述中的每个组件都有对应的public访问方法。方法的名称与组件名称相同。
- 一个包含全部组件的公开构造器,用来初始化对应组件。
- 实现了equals()和hashCode()方法。equals()要求全部组件都必须相等。
- 实现了toString(),输出全部组件的信息。
Records 还支持自定义方法和验证
public record BankAccount(String accountNumber, double balance) {
// 紧凑构造函数 - 用于验证
public BankAccount {
if (balance < 0) {
throw new IllegalArgumentException("余额不能为负数");
}
if (accountNumber == null || accountNumber.isBlank()) {
throw new IllegalArgumentException("账号不能为空");
}
}
// 自定义方法
public boolean isVIP() {
return balance > 100000;
}
// 静态工厂方法
public static BankAccount createSavingsAccount(String accountNumber) {
return new BankAccount(accountNumber, 0.0);
}
}文本块(第二次预览)
Java 14 继续完善文本块特性,添加了两个转义序列:
// \s - 表示空格
String text1 = """
Hello\sWorld
""";
// 结果: "Hello World\n"
// \ - 行末转义,避免换行
String text2 = """
This is a very long line that \
continues on the next line
""";
// 结果: "This is a very long line that continues on the next line\n"孵化器特性
打包工具(孵化器)
Java 14 引入了 jpackage 工具作为孵化器特性,可以将 Java 应用打包成平台特定的安装包:
# 创建可执行文件
jpackage --input target/ \
--name MyApp \
--main-jar myapp.jar \
--main-class com.example.Main
# 创建 Windows 安装包
jpackage --input target/ \
--name MyApp \
--main-jar myapp.jar \
--main-class com.example.Main \
--type msi
# 创建 macOS 安装包
jpackage --input target/ \
--name MyApp \
--main-jar myapp.jar \
--main-class com.example.Main \
--type dmg外部内存访问 API(孵化器)
Java 14 引入了外部内存访问 API,提供安全高效的堆外内存操作
import jdk.incubator.foreign.*;
// 分配堆外内存
try (MemorySegment segment = MemorySegment.allocateNative(1024)) {
MemoryAddress address = segment.baseAddress();
// 写入数据
MemoryAccess.setIntAtOffset(segment, 0, 42);
MemoryAccess.setIntAtOffset(segment, 4, 24);
// 读取数据
int value1 = MemoryAccess.getIntAtOffset(segment, 0);
int value2 = MemoryAccess.getIntAtOffset(segment, 4);
System.out.println("值1: " + value1 + ", 值2: " + value2);
}
// 内存自动释放Java 15新特性(2020年9月)
正式特性
文本块
文本块由预览版变为正式版
文本块会保持代码的缩进、而且内部的引号不需要转义。
配合 String 的格式化方法,就能轻松传入参数生成复杂的字符串模板
// HTML 模板
String htmlTemplate = """
<!DOCTYPE html>
<html>
<head>
<title>%s</title>
</head>
<body>
<h1>欢迎 %s</h1>
<p>这是一个使用文本块的示例</p>
</body>
</html>
""";
String html = htmlTemplate.formatted("我的网站", "Seven");
// SQL 查询
String sql = """
SELECT u.name, u.email, p.title
FROM users u
JOIN posts p ON u.id = p.user_id
WHERE u.status = 'ACTIVE'
AND p.published_at > ?
ORDER BY p.created_at DESC
LIMIT 10
""";
// JSON 数据
String json = """
{
"name": "Seven",
"age": 30,
"skills": ["Java", "Python", "JavaScript"],
"address": {
"city": "北京",
"district": "海淀区"
}
}
""";文本块的处理方法
Java 15 为文本块提供了几个有用的处理方法
String textBlock = """
第一行
缩进的第二行
第三行
""";
// stripIndent() - 去除公共缩进
String stripped = textBlock.stripIndent();
// translateEscapes() - 处理转义序列
String withEscapes = """
第一行\\n第二行\\t制表符
""";
String translated = withEscapes.translateEscapes();
// formatted() - 格式化文本块
String template = """
用户名: %s
年龄: %d
邮箱: %s
""";
String result = template.formatted("seven", 25, "seven@example.com");Hidden 隐藏类
Java 15 引入了 Hidden 隐藏类特性,这是一个 专为框架和运行时环境设计 的底层机制,主要是为了优化 动态生成短期类(比如 Lambda 表达式、动态代理)的性能问题,普通开发者无需关心。
在 Lambda 表达式、AOP 动态代理、ORM 映射等场景中,框架会动态生成代码载体(比如方法句柄、临时代理类),这些载体需要关联类的元数据才能运行。如果生成频繁,传统类的元数据会被类加载器追踪,需要等待类加载器卸载才能回收,导致元空间堆积和 GC 压力。
Hidden 类的特点是对其定义类加载器之外的所有代码都不可见,由于不可发现且链接微弱,M 垃圾回收器能够更高效地卸载隐藏类及其元数据,从而防止短期类堆积对元空间造成压力,优化了需要动态生成大量类的性能。
Hidden 类的特点
- 不可发现性:不能通过反射 API 发现
- 弱链接:与类加载器的连接很弱,便于回收
- 访问控制:只能被定义它的类访问
- 性能优化:减少元空间压力
// 这是框架层面的 API,普通开发者不需要直接使用
import java.lang.invoke.MethodHandles;
public class HiddenClassExample {
public static void main(String[] args) throws Exception {
// 获取 Lookup 对象
MethodHandles.Lookup lookup = MethodHandles.lookup();
// 动态生成的字节码(简化示例)
byte[] classBytes = generateDynamicClass();
// 定义隐藏类
MethodHandles.Lookup hiddenLookup = lookup.defineHiddenClass(
classBytes,
true, // 是否初始化
MethodHandles.Lookup.ClassOption.NESTMATE
);
Class<?> hiddenClass = hiddenLookup.lookupClass();
System.out.println("隐藏类: " + hiddenClass.getName());
// 隐藏类不能通过 Class.forName() 找到
try {
Class.forName(hiddenClass.getName());
} catch (ClassNotFoundException e) {
System.out.println("隐藏类无法通过反射发现");
}
}
private static byte[] generateDynamicClass() {
// 实际场景中,这里会是动态生成的字节码
// 比如 Lambda 表达式、动态代理等
return new byte[0]; // 简化示例
}
}Edwards 曲线数字签名算法
Java 15 添加了对 EdDSA(Edwards-curve Digital Signature Algorithm)的支持
import java.security.*;
import java.security.spec.EdDSAParameterSpec;
public class EdDSAExample {
public static void main(String[] args) throws Exception {
// 生成 Ed25519 密钥对
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("Ed25519");
KeyPair keyPair = keyGen.generateKeyPair();
// 创建签名对象
Signature signature = Signature.getInstance("Ed25519");
// 签名
signature.initSign(keyPair.getPrivate());
byte[] message = "Hello EdDSA!".getBytes();
signature.update(message);
byte[] signatureBytes = signature.sign();
// 验证签名
signature.initVerify(keyPair.getPublic());
signature.update(message);
boolean isValid = signature.verify(signatureBytes);
System.out.println("签名验证: " + isValid);
}
}EdDSA 的优势
- 性能更好:比传统的 ECDSA 更快
- 安全性高:抗侧信道攻击
- 确定性:相同输入产生相同签名
ZGC 和 Shenandoah 正式化
Java 15 将 ZGC和 Shenandoah 从实验特性升级为正式特性
# ZGC 现在可以在生产环境使用
java -XX:+UseZGC MyApp
# Shenandoah 也正式可用
java -XX:+UseShenandoahGC MyApp重新实现传统 DatagramSocket API
Java 15 重新实现了 DatagramSocket 和 MulticastSocket
// API 使用方式不变,但底层实现更现代化
DatagramSocket socket = new DatagramSocket(8080);
MulticastSocket multicastSocket = new MulticastSocket(8080);
// 新实现的优势:
// 1. 更好的可维护性
// 2. 为 Project Loom 做准备
// 3. 更好的性能表现禁用和弃用偏向锁
# 偏向锁默认禁用
# 如果需要启用(不推荐)
java -XX:+UseBiasedLocking MyApp偏向锁定的问题
- 增加了 JVM 复杂性
- 在现代多线程应用中收益有限
- 与其他 JVM 特性存在冲突
删除 Nashorn JavaScript 引擎
Java 15 完全移除了 Nashorn JavaScript 引擎
// 这些 API 在 Java 15 中已被移除
// ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");推荐使用独立的 JavaScript引擎,如 GraalVM 的 JavaScript 实现。
预览特性
Sealed Classes(密封类)
密封类和接口,作用是限制一个类可以由哪些子类继承或者实现。
- 如果指定模块的话,sealed class和其子类必须在同一个模块下。如果没有指定模块,则需要在同一个包下。
- sealed class指定的子类必须直接继承该sealed class。
- sealed class的子类要用final修饰。
- sealed class的子类如果不想用final修饰的话,可以将子类声明为sealed class。
Animal类,在指定允许继承的子类时可以使用全限定名
// 只允许Cat和Dog继承
public sealed class Animal permits Cat, Dog{ // 多个子类之间用,隔开。
public void eat(){}
}Cat类
// 由于Animal只能由Cat和Dog继承,因此Cat就不能有子类了,因此Cat类需要被修饰为final
public final class Cat extends Animal{
public void eat(){
System.out.println("123");
}
}Dog类
// Dog类可以有子类,那么就将Dog也修饰为sealed
public sealed class Dog extends Animal
permits Husky {}Husky类
public final class Husky extends Dog{
}Test类
public class Test{
public static void main(String[] args){
Cat c = new Cat();
c.eat();
Dog d = new Dog();
}
}instanceof模式匹配(第二次预览)
// 改进的模式匹配
public void processValue(Object obj) {
// 基本模式匹配
if (obj instanceof String str) {
System.out.println("字符串: " + str);
}
// 结合逻辑运算符
if (obj instanceof String str && str.length() > 5) {
System.out.println("长字符串: " + str);
}
// 在表达式中使用
String result = obj instanceof String str ? str.toUpperCase() : "非字符串";
// 否定形式
if (!(obj instanceof String str)) {
System.out.println("不是字符串");
return;
}
// 这里 str 可用,因为前面的否定条件
System.out.println("确认是字符串: " + str);
}record类型(第二次预览)
// Records 支持泛型
public record Pair<T, U>(T first, U second) {
// 静态方法
public static <T, U> Pair<T, U> of(T first, U second) {
return new Pair<>(first, second);
}
}
// 使用示例
Pair<String, Integer> nameAge = Pair.of("张三", 25);
System.out.println(nameAge.first()); // 张三
System.out.println(nameAge.second()); // 25
// Records 可以实现接口
public interface Drawable {
void draw();
}
public record Point(int x, int y) implements Drawable {
@Override
public void draw() {
System.out.println("在 (" + x + ", " + y + ") 处绘制点");
}
}孵化器特性
外部内存访问 API(第二次孵化器)
Java 15 继续完善外部内存访问 API
import jdk.incubator.foreign.*;
public class ForeignMemoryExample {
public static void main(String[] args) {
// 分配本地内存段
try (MemorySegment segment = MemorySegment.allocateNative(1024)) {
// 创建内存访问句柄
VarHandle intHandle = MemoryHandles.varHandle(int.class, ByteOrder.nativeOrder());
// 写入数据
intHandle.set(segment, 0L, 42);
intHandle.set(segment, 4L, 24);
// 读取数据
int value1 = (int) intHandle.get(segment, 0L);
int value2 = (int) intHandle.get(segment, 4L);
System.out.println("值1: " + value1 + ", 值2: " + value2);
}
}
}Java16新特性(2021年3月)
正式特性
Records
以前,我们如果想创建一个 POJO 对象来存一些数据,需要编写大量的样板代码,包括构造函数、getter 方法、equals、hashCode、toString 等等,比较麻烦。
// Java 16 之前创建数据类的方式
public class Person {
private final String name;
private final int age;
private final String email;
public Person(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
public String getName() { return name; }
public int getAge() { return age; }
public String getEmail() { return email; }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age &&
Objects.equals(name, person.name) &&
Objects.equals(email, person.email);
}
@Override
public int hashCode() {
return Objects.hash(name, age, email);
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", email='" + email + "'}";
}
}即使通过 Lombok 插件简化了代码,估计也要十几行。
有了 Java 16 的 Records,创建数据包装类简直不要太简单,一行代码搞定
public record Person(String name, int age, String email) {}Records 自动提供了所有必需的方法,使用方式完全一样
Person person = new Person("seven", 25, "seven@yuyuanweb.com");
System.out.println(person.name()); // 自动生成的访问器
System.out.println(person.age());
System.out.println(person.email());
System.out.println(person.toString()); // 自动生成的 toString此外,Records 还支持自定义方法和验证逻辑,只不过个人建议这种情况下不如老老实实用"类"了。
public record BankAccount(String accountNumber, double balance) {
// 构造函数中添加验证
public BankAccount {
if (balance < 0) {
throw new IllegalArgumentException("余额不能为负数");
}
if (accountNumber == null || accountNumber.isBlank()) {
throw new IllegalArgumentException("账号不能为空");
}
}
// 自定义方法
public boolean isVIP() {
return balance > 100000;
}
// 静态工厂方法
public static BankAccount createSavingsAccount(String accountNumber) {
return new BankAccount(accountNumber, 0.0);
}
}instanceof 模式匹配
Java 16 正式推出了 instanceof 的模式匹配,让类型检查和转换变得更优雅。
public String processValue(Object value) {
// 基本模式匹配
if (value instanceof String str) {
return "字符串: " + str;
}
// 结合条件判断
if (value instanceof String str && str.length() > 5) {
return "长字符串: " + str;
}
// 结合逻辑运算符
if (value instanceof Integer num && num > 0) {
return "正整数: " + num;
}
// 否定形式的作用域
if (!(value instanceof String str)) {
return "非字符串类型";
}
// 这里 str 变量可用,因为前面的条件确保了类型
return "确认的字符串: " + str;
}
// 在三元运算符中使用
public String formatValue(Object obj) {
return obj instanceof String str ? str.toUpperCase() : obj.toString();
}Stream 新增方法
Java 16 为 Stream APl 添加了 tolist()方法,可以用更简洁的代码将流转换为不可变列表。
// 传统写法
List<String> result = stream
.filter(s -> s.length() > 3)
.collect(Collectors.toList());
// Java 16 简化写法
List<String> result = stream
.filter(s -> s.length() > 3)
.toList(); // 返回不可变 List还提供了 mapMulti()方法,跟 fatMap 的作用一样,将一个元素映射为0个或多个元素,但是某些场景下比 fatMap 更灵活高效。
当需要从一个元素生成多个元素时,fatMap 需要先创建一个中间 Stream,而 mapMulti()可以通过传入的 Consumer 直接“推送" 多个元素,避免了中间集合或 Stream 的创建开销。
// flatMap 传统方式
List<String> words = List.of("hello", "world", "java");
List<Character> chars = words.stream()
.flatMap(word -> word.chars()
.mapToObj(c -> (char) c))
.toList();
// Java 16 的 mapMulti 方式
List<Character> chars = words.stream()
.<Character>mapMulti((word, consumer) -> {
for (char c : word.toCharArray()) {
consumer.accept(c); // 直接向下游推送元素
}
})
.toList();mapMulti() 的实际应用
// 过滤并展开嵌套数据
List<List<String>> nestedList = List.of(
List.of("a", "b", "c"),
List.of("d", "e"),
List.of("f", "g", "h", "i")
);
// 只保留长度大于2的子列表,并展开
List<String> result = nestedList.stream()
.<String>mapMulti((subList, consumer) -> {
if (subList.size() > 2) {
subList.forEach(consumer);
}
})
.toList();
System.out.println(result); // [a, b, c, f, g, h, i]打包工具正式化
Java 16 将jpackage 工具正式化,可以创建平台特定的安装包
# 创建应用程序镜像
jpackage --input target/ \
--name MyApp \
--main-jar myapp.jar \
--main-class com.example.Main \
--type app-image
# 创建安装程序
jpackage --input target/ \
--name MyApp \
--main-jar myapp.jar \
--main-class com.example.Main \
--type msi \
--app-version 1.0 \
--description "我的应用程序"Unix 域套接字通道
Java 16 添加了对 Unix 域套接字的支持
import java.net.UnixDomainSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Path;
// 服务器端
Path socketPath = Path.of("/tmp/myapp.socket");
UnixDomainSocketAddress address = UnixDomainSocketAddress.of(socketPath);
try (ServerSocketChannel serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX)) {
serverChannel.bind(address);
// 接受连接
try (SocketChannel clientChannel = serverChannel.accept()) {
// 处理客户端连接
}
}
// 客户端
try (SocketChannel clientChannel = SocketChannel.open(StandardProtocolFamily.UNIX)) {
clientChannel.connect(address);
// 与服务器通信
}Unix 域套接字的优势
- 性能更好:本地通信无需网络栈
- 安全性高:基于文件系统权限
- 可靠性强:不受网络问题影响
Alpine Linux 端囗
Java 16 正式支持 Alpine Linux,这对容器化部署很重要
# 现在可以使用 Alpine 作为基础镜像
FROM openjdk:16-jdk-alpine
COPY myapp.jar /app/
WORKDIR /app
CMD ["java", "-jar", "myapp.jar"]Alpine Linux 的优势
- 体积小:基础镜像只有几 MB
- 安全性高:攻击面小
- 适合容器:启动快,资源占用少
弹性元空间
Java 16 改进了元空间的内存管理,可以更及时地将未使用的内存返回给操作系统
# 元空间内存管理是自动的,无需特殊配置
java -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=256m MyApp这个改进可以:
- 减少长期运行应用的内存占用提高容器环境下的内存利用率
- 降低内存压力
默认强封装 JDK内部
Java 16 默认强封装 JDK 内部 API,提高安全性
# 如果需要访问内部 API(不推荐)
java --add-opens java.base/sun.nio.ch=ALL-UNNAMED MyApp
# 或者允许所有模块访问(非常不推荐)
java --permit-illegal-access MyApp这个改变鼓励开发者
- 使用标准 API而不是内部 API
- 迁移到公开的替代方案
- 提高代码的可移植性
包装类构造方法的警告
使用包装类的构造方法在编译的时候会出现警告,不建议再使用包装类的构造方法。下面代码在javac编译之后会出现警告。
Integer i = new Integer(8);不建议使用包装类作为锁对象,倘若使用包装类作为锁对象,在编译时会出现警告。
Integer i = 8;
synchronized(i){
}新增日时段
在DateTimeFormatter.ofPattern传入B可以获取现在时间对应的日时段,上午,下午等
System.out.println(DateTimeFormatter.ofPattern("B").format(LocalDateTime.now()));InvocationHandler新增方法
在该接口中添加了下面方法
public static Object invokeDefault(Object proxy, Method method, Object... args)该方法可以调用父接口中defalut方法,比如有下面接口
interface Girl{
default void eat(){
System.out.println("cucumber");
}
}实现类
public class Lucy implements Girl{
public void eat(){
System.out.println("banana");
}
}测试类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Test{
public static void main(String[] args) {
Girl girl = new Lucy();
//不使用invokeDefault会调用重写的eat方法
Girl proxy1 = (Girl)Proxy.newProxyInstance(girl.getClass().getClassLoader(),girl.getClass().getInterfaces(),
(obj,method,params)->{
Object invoke = method.invoke(girl);
return invoke;
});
proxy1.eat();
//使用invokeDefault会调用父接口中的default方法
Girl proxy2 = (Girl)Proxy.newProxyInstance(Girl.class.getClassLoader(),new Class<?>[]{Girl.class},
(obj,method,params)->{
if (method.isDefault()) {
return InvocationHandler.invokeDefault(obj, method, params);
}
return null;
});
proxy2.eat();
}
}预览特性
密封类(第二次预览)
Java 16 继续完善密封类特性
// 密封接口
public sealed interface Vehicle
permits Car, Truck, Motorcycle {
void start();
}
// 实现类必须选择策略
public final class Car implements Vehicle {
@Override
public void start() {
System.out.println("汽车启动");
}
}
public sealed class Truck implements Vehicle
permits DeliveryTruck, SemiTruck {
@Override
public void start() {
System.out.println("卡车启动");
}
}
public non-sealed class Motorcycle implements Vehicle {
@Override
public void start() {
System.out.println("摩托车启动");
}
}密封类的实际应用
// 表示不同类型的 HTTP 响应
public sealed interface HttpResponse
permits SuccessResponse, ErrorResponse, RedirectResponse {
}
public record SuccessResponse(int statusCode, String body) implements HttpResponse {}
public record ErrorResponse(int statusCode, String message) implements HttpResponse {}
public record RedirectResponse(int statusCode, String location) implements HttpResponse {}
// 处理响应的方法
public void handleResponse(HttpResponse response) {
switch (response) {
case SuccessResponse(var code, var body) ->
System.out.println("成功: " + code + " - " + body);
case ErrorResponse(var code, var message) ->
System.err.println("错误: " + code + " - " + message);
case RedirectResponse(var code, var location) ->
System.out.println("重定向: " + code + " -> " + location);
// 不需要 default,因为所有情况都覆盖了
}
}孵化器特性
向量 API(孵化器)
Java 16 引入了向量 API,可以利用现代 CPU的 SIMD 指令
import jdk.incubator.vector.*;
public class VectorExample {
public static void main(String[] args) {
VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256;
float[] a = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
float[] b = {8.0f, 7.0f, 6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 1.0f};
float[] result = new float[8];
// 向量化加法
for (int i = 0; i < a.length; i += SPECIES.length()) {
var va = FloatVector.fromArray(SPECIES, a, i);
var vb = FloatVector.fromArray(SPECIES, b, i);
var vc = va.add(vb);
vc.intoArray(result, i);
}
System.out.println(Arrays.toString(result));
// [9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0]
}
}向量 API的优势
- 性能提升:利用 SIMD 指令并行计算。
- 跨平台:JVM 自动选择合适的指令
- 类型安全:编译时检查向量操作
外部链接器 API(孵化器)
Java 16 引入了外部链接器 API,可以更安全地调用本地代码

