Java 15 新特性解析与代码示例
文章目录
- Java 15 新特性解析与代码示例
- 引言
- 1. 密封类(Sealed Classes)
- 1.1. 什么是密封类?
- 1.2. 为什么使用密封类?
- 1.3. 语法
- 1.4. 与传统方法的对比
- 1.5. 使用场景
- 1.6. 示例:结合模式匹配
- 2. `instanceof` 模式匹配(Pattern Matching for instanceof)
- 2.1. 什么是模式匹配?
- 2.2. 语法
- 2.3. 优点
- 2.4. 与密封类的结合
- 2.5. 示例
- 3. 记录(Records)
- 3.1. 什么是记录?
- 3.2. 语法
- 3.3. 优点
- 3.4. 使用场景
- 3.5. 示例
- 4. 文本块(Text Blocks)
- 4.1. 什么是文本块?
- 4.2. 语法
- 4.3. 优点
- 4.4. 缩进处理
- 4.5. 示例
- 5. 隐藏类(Hidden Classes)
- 5.1. 什么是隐藏类?
- 5.2. 为什么重要?
- 5.3. 示例
- 6. 外部内存访问API(Foreign-Memory Access API)
- 6.1. 什么是外部内存访问API?
- 6.2. 用途
- 6.3. 示例
- 7. 垃圾回收器:ZGC和Shenandoah
- 7.1. ZGC
- 7.2. Shenandoah
- 7.3. 选择建议
- 8. Unicode 13.0支持
- 9. CharSequence的`isEmpty`方法
- 10. EdDSA加密支持
- 10.1. 什么是EdDSA?
- 10.2. 示例
- 11. 其他特性
- 12. 结语
引言
Java 15(JDK 15)于2020年9月15日发布,作为Java快速发布周期(每六个月一次)的一部分,带来了多项新特性和改进。这些特性涵盖语言增强、API改进和JVM优化,旨在提升开发者的生产力、代码可读性和程序性能。本文将深入探讨Java 15的关键特性,包括密封类、模式匹配、记录、文本块等,并为每个特性提供详细的代码示例和与传统方法的对比。我们还将介绍其他重要更新,如隐藏类、外部内存访问API、垃圾回收器改进等。
本文的目标是为开发者提供"硬核"且实用的内容,确保读者在阅读后能够理解每个特性的用途并能将其应用于实际项目中。需要注意的是:
- 密封类、模式匹配和记录是预览特性,需使用
--enable-preview
标志启用 - 外部内存访问API是孵化特性,需使用
--add-modules jdk.incubator.foreign
启用 - 部分特性在不同Java版本中的状态不同,使用时需注意版本兼容性
以下是Java 15的主要新特性列表:
- 密封类(Sealed Classes,JEP 360,预览特性)
instanceof
模式匹配(Pattern Matching for instanceof,JEP 375,预览特性)- 记录(Records,JEP 384,预览特性)
- 文本块(Text Blocks,JEP 378,正式特性)
- 隐藏类(Hidden Classes,JEP 371)
- 外部内存访问API(Foreign-Memory Access API,JEP 383,孵化特性)
- ZGC和Shenandoah垃圾回收器(JEP 377和JEP 379,生产就绪)
- Unicode 13.0支持
- CharSequence的
isEmpty
方法 - EdDSA加密支持(JEP 339)
让我们逐一深入分析这些特性。
1. 密封类(Sealed Classes)
1.1. 什么是密封类?
密封类是Java 15引入的预览特性(JEP 360),允许开发者限制哪些类可以扩展某个类或实现某个接口。通过使用sealed
关键字和permits
子句,开发者可以明确指定允许的子类,从而控制继承层次。这种机制特别适合需要严格定义类层次结构的场景,例如在领域建模中限制可能的子类型。
1.2. 为什么使用密封类?
密封类提供了以下优势:
- 控制继承:防止意外的子类扩展,确保只有指定的类可以继承。
- 穷尽性模式匹配:与模式匹配结合使用时,编译器可以验证是否处理了所有可能的子类。
- 代码清晰性:明确类层次结构,减少维护复杂性。
- 性能优化:JVM可以利用密封类的有限子类信息进行优化。
1.3. 语法
密封类的定义使用sealed
关键字,并在permits
子句中列出允许的子类。子类必须是final
、sealed
或non-sealed
之一。
non-sealed
允许子类被自由扩展,提供了密封类层次结构中的灵活性。
示例:
public sealed class Shape permits Circle, Rectangle, Triangle {public abstract void draw();
}public final class Circle extends Shape {@Overridepublic void draw() {System.out.println("Drawing a circle");}
}public final class Rectangle extends Shape {@Overridepublic void draw() {System.out.println("Drawing a rectangle");}
}public non-sealed class Triangle extends Shape {@Overridepublic void draw() {System.out.println("Drawing a triangle");}
}// Triangle是非密封的,可以自由扩展
class RightTriangle extends Triangle {@Overridepublic void draw() {System.out.println("Drawing a right triangle");}
}
在这个例子中,Shape
是一个密封类,只允许Circle
、Rectangle
和Triangle
扩展它。Triangle
被声明为non-sealed
,因此可以进一步被RightTriangle
扩展。
1.4. 与传统方法的对比
在Java 15之前,控制继承通常有以下方法:
- 使用
final
类:完全禁止扩展,但无法允许任何子类。 - 使用包访问控制:通过将类放在同一包中并限制访问权限来控制继承,但这种方法不够灵活。
- 使用接口和实现类:但无法限制哪些类可以实现接口。
密封类通过sealed
、permits
、final
和non-sealed
的组合,提供了更灵活和明确的继承控制机制。
1.5. 使用场景
密封类在以下场景中特别有用:
- 领域建模:例如,在金融系统中定义一组固定的交易类型。
- API设计:限制哪些类可以实现某个接口,确保API的正确使用。
- 模式匹配:与
instanceof
模式匹配结合,实现穷尽性检查。
1.6. 示例:结合模式匹配
密封类与模式匹配结合可以实现更安全的类型处理。以下是一个完整示例:
public sealed class Shape permits Circle, Rectangle {public abstract double area();
}public final class Circle extends Shape {private final double radius;public Circle(double radius) {this.radius = radius;}@Overridepublic double area() {return Math.PI * radius * radius;}
}public final class Rectangle extends Shape {private final double width, height;public Rectangle(double width, double height) {this.width = width;this.height = height;}@Overridepublic double area() {return width * height;}
}public class ShapeCalculator {public static void printArea(Shape shape) {// 编译器知道Shape只有Circle和Rectangle两种子类if (shape instanceof Circle c) {System.out.println("Circle area: " + c.area());} else if (shape instanceof Rectangle r) {System.out.println("Rectangle area: " + r.area());}// 不需要else分支,因为所有可能性都已处理}public static void main(String[] args) {Shape circle = new Circle(5.0);Shape rectangle = new Rectangle(4.0, 6.0);printArea(circle); // 输出:Circle area: 78.53981633974483printArea(rectangle); // 输出:Rectangle area: 24.0}
}
注意:由于密封类是预览特性,编译和运行时需使用--enable-preview
标志:
javac --enable-preview --release 15 ShapeCalculator.java
java --enable-preview ShapeCalculator
2. instanceof
模式匹配(Pattern Matching for instanceof)
2.1. 什么是模式匹配?
instanceof
模式匹配是Java 15的预览特性(JEP 375),允许在类型检查的同时声明一个变量,直接使用该变量而无需显式类型转换。这减少了样板代码,提高了代码的可读性和安全性。
2.2. 语法
传统方式:
if (obj instanceof String) {String str = (String) obj; // 需要显式转换System.out.println(str.toUpperCase());
}
使用模式匹配:
if (obj instanceof String str) { // 自动转换并赋值给strSystem.out.println(str.toUpperCase());// str的作用域仅限于这个if块内
}
在模式匹配中,str
是一个绑定变量,如果obj
是String
类型,则自动转换为String
并赋值给str
。注意变量的作用域仅限于对应的if块内。
2.3. 优点
- 简洁性:将类型检查和转换合并为一行。
- 可读性:代码更直观,逻辑更清晰。
- 安全性:避免了手动类型转换可能导致的
ClassCastException
。
2.4. 与密封类的结合
模式匹配与密封类结合时,编译器可以确保所有可能的子类都被处理。例如:
public void handleShape(Shape shape) {if (shape instanceof Circle c) {c.draw();} else if (shape instanceof Rectangle r) {r.draw();}// 不需要else分支,编译器知道Shape只有这两种子类
}
2.5. 示例
以下是一个结合密封类和模式匹配的完整示例:
public class PatternMatchingExample {public static void process(Object obj) {if (obj instanceof String s) {System.out.println("String length: " + s.length());} else if (obj instanceof Integer i && i > 0) {System.out.println("Positive integer: " + i);} else if (obj instanceof Double d) {System.out.println("Double value: " + d);} else {System.out.println("Unknown type: " + obj.getClass());}}public static void main(String[] args) {process("Hello"); // 输出:String length: 5process(42); // 输出:Positive integer: 42process(3.14); // 输出:Double value: 3.14process(new Object());// 输出:Unknown type: class java.lang.Object}
}
注意:模式匹配是预览特性,需启用--enable-preview
。
3. 记录(Records)
3.1. 什么是记录?
记录(Records)是Java 14引入并在Java 15继续作为预览特性的功能(JEP 384)。记录是一种特殊的类,专为携带不可变数据设计,自动生成构造函数、访问器、toString
、equals
和hashCode
方法。
记录有以下限制:
- 隐含final,不能是abstract
- 不能显式扩展其他类(隐含扩展Record类)
- 所有字段都是final的
- 不能声明实例字段(只能在记录头中声明)
3.2. 语法
记录的定义非常简洁:
public record Point(int x, int y) {}
这等同于以下传统类:
public final class Point {private final int x;private final int y;public Point(int x, int y) {this.x = x;this.y = y;}public int x() { return x; }public int y() { return y; }@Overridepublic String toString() {return "Point[x=" + x + ", y=" + y + "]";}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Point point = (Point) o;return x == point.x && y == point.y;}@Overridepublic int hashCode() {return Objects.hash(x, y);}
}
3.3. 优点
- 简洁性:减少了大量样板代码。
- 不可变性:记录默认不可变,适合线程安全和功能式编程。
- 一致性:自动生成的方法遵循标准约定。
3.4. 使用场景
记录适用于:
- 数据传输对象(DTO):在层与层之间传递数据。
- 元组:临时组合多个值。
- 功能式编程:与Stream API等结合使用。
3.5. 示例
以下是一个使用记录计算两点距离的示例:
public record Point(int x, int y) {// 可以在记录中添加自定义方法public double distanceTo(Point other) {int dx = x - other.x();int dy = y - other.y();return Math.sqrt(dx * dx + dy * dy);}
}public class PointDistance {public static void main(String[] args) {Point p1 = new Point(0, 0);Point p2 = new Point(3, 4);// 使用自动生成的toString()System.out.println("Point 1: " + p1); // 输出:Point 1: Point[x=0, y=0]// 使用自定义方法double dist = p1.distanceTo(p2);System.out.println("Distance: " + dist); // 输出:Distance: 5.0// 使用自动生成的equals()System.out.println("Equal points: " + p1.equals(new Point(0, 0))); // 输出:true}
}
注意:记录是预览特性,需启用--enable-preview
。
4. 文本块(Text Blocks)
4.1. 什么是文本块?
文本块(Text Blocks)是Java 13引入预览、Java 14二次预览并在Java 15成为正式特性的功能(JEP 378)。它允许开发者以自然的方式定义多行字符串,无需使用转义字符或字符串拼接。
4.2. 语法
文本块使用三重引号("""
)定义:
String html = """<html><body><p>Hello, world</p></body></html>""";
文本块中的空白行会被保留。如果一行只包含缩进空格,它会被视为空白行而不是空字符串。
4.3. 优点
- 可读性:多行字符串更直观。
- 减少转义:无需手动转义引号或换行符。
- 缩进控制:支持自然的代码缩进。
4.4. 缩进处理
文本块默认保留代码中的缩进。如果需要去除前导空格,可以使用String.stripIndent()
方法:
String html = """<html><body><p>Hello</p></body></html>""".stripIndent();
4.5. 示例
以下是一个使用文本块定义JSON的示例:
String json = """{"name": "John","age": 30,"city": "New York","hobbies": ["reading","traveling"]}""";System.out.println(json);
SQL查询示例:
String query = """SELECT u.id, u.name, COUNT(o.id) AS order_countFROM users uLEFT JOIN orders o ON u.id = o.user_idWHERE u.active = trueGROUP BY u.id, u.nameHAVING COUNT(o.id) > 0ORDER BY order_count DESC""";
5. 隐藏类(Hidden Classes)
5.1. 什么是隐藏类?
隐藏类(Hidden Classes,JEP 371)是Java 15引入的特性,允许JVM在运行时创建不可见的类。这些类不注册在常量池中,无法通过反射访问,主要用于优化JVM内部实现,如lambda表达式和方法句柄。
5.2. 为什么重要?
隐藏类提高了性能和内存效率:
- 减少内存占用:避免创建命名类的开销。
- 提高封装性:隐藏实现细节,防止外部访问。
- 优化堆栈跟踪:使lambda表达式的堆栈跟踪更清晰。
开发者也可以通过MethodHandles.Lookup.defineHiddenClass
方法显式创建隐藏类,但这通常只在需要高度动态类加载的高级场景中使用。
5.3. 示例
隐藏类通常由JVM内部使用,例如:
List<String> list = Arrays.asList("a", "b", "c");
list.forEach(s -> System.out.println(s)); // lambda由隐藏类实现
6. 外部内存访问API(Foreign-Memory Access API)
6.1. 什么是外部内存访问API?
外部内存访问API(JEP 383)是Java 15的孵化特性,允许Java程序安全高效地访问堆外内存(如本地内存)。它是Project Panama的一部分,旨在改善Java与本地代码的互操作性。
6.2. 用途
- 高性能数据处理:直接操作大块数据,避免复制到Java堆。
- 本地库交互:与C/C++库共享内存。
- 内存映射文件:直接访问文件内容。
6.3. 示例
以下是一个安全使用外部内存访问API的示例:
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.ResourceScope;public class ForeignMemoryExample {public static void main(String[] args) {try {try (ResourceScope scope = ResourceScope.newConfinedScope()) {MemorySegment segment = MemorySegment.allocateNative(10, scope);// 写入数据for (int i = 0; i < 10; i++) {segment.setAtIndex(byte.class, i, (byte)(65 + i));}// 读取数据for (int i = 0; i < 10; i++) {System.out.print((char) segment.getAtIndex(byte.class, i));}// 输出:ABCDEFGHIJ}} catch (Exception e) {e.printStackTrace();}}
}
注意:此特性需要启用孵化模块:
javac --add-modules jdk.incubator.foreign ForeignMemoryExample.java
java --add-modules jdk.incubator.foreign ForeignMemoryExample
7. 垃圾回收器:ZGC和Shenandoah
7.1. ZGC
ZGC(Z Garbage Collector,JEP 377)是低延迟、高可扩展性的垃圾回收器,适合大堆内存和低暂停时间需求的应用。在Java 15中,ZGC正式成为生产就绪特性。
启用方式:
java -XX:+UseZGC YourApp
7.2. Shenandoah
Shenandoah(JEP 379)是另一个低延迟垃圾回收器,强调并发标记和压缩。在Java 15中也成为生产就绪特性。
启用方式:
java -XX:+UseShenandoahGC YourApp
7.3. 选择建议
垃圾回收器 | 最大堆大小 | 平均暂停时间 | 适用场景 |
---|---|---|---|
ZGC | 16TB | <10ms | 超大堆、极低延迟 |
Shenandoah | 数TB | <10ms | 大堆、平衡吞吐量 |
G1 | 数十GB | <200ms | 大多数通用应用 |
8. Unicode 13.0支持
Java 15支持Unicode 13.0,新增5930个字符(总计143859个),4个新脚本(总计154个)和55个新emoji字符。这对国际化应用至关重要,确保Java能处理最新的字符和脚本。
9. CharSequence的isEmpty
方法
Java 15为CharSequence
接口添加了默认方法isEmpty()
,用于检查字符序列是否为空。
示例:
CharSequence seq = "";
if (seq.isEmpty()) { // 比seq.length() == 0更直观System.out.println("Sequence is empty");
}
10. EdDSA加密支持
10.1. 什么是EdDSA?
EdDSA(Edwards-curve Digital Signature Algorithm,JEP 339)是Java 15引入的现代数字签名算法,基于椭圆曲线加密,提供高性能和高安全性。
EdDSA相比传统ECDSA有以下优势:
- 更高的性能
- 更强的安全性
- 更简单的密钥生成
- 天然抵抗侧信道攻击
10.2. 示例
以下是使用EdDSA签名和验证的示例:
import java.security.*;
import java.util.Base64;public class EdDSAExample {public static void main(String[] args) throws Exception {// 生成密钥对KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519");KeyPair kp = kpg.generateKeyPair();// 签名Signature sig = Signature.getInstance("Ed25519");sig.initSign(kp.getPrivate());byte[] message = "Important message".getBytes();sig.update(message);byte[] signature = sig.sign();System.out.println("Signature: " + Base64.getEncoder().encodeToString(signature));// 验证sig.initVerify(kp.getPublic());sig.update(message);boolean verified = sig.verify(signature);System.out.println("Verified: " + verified); // 输出:true}
}
11. 其他特性
- Nashorn JavaScript引擎移除(JEP 372):Nashorn引擎被移除,建议使用GraalVM等替代方案。
- RMI激活机制弃用(JEP 385):RMI激活机制被标记为即将移除。
- DatagramSocket API重实现(JEP 373):优化了旧的
DatagramSocket
实现,提高性能。
12. 结语
Java 15通过引入密封类、模式匹配、记录和文本块等特性,显著提升了语言的表达力和开发效率。隐藏类和外部内存访问API优化了JVM性能,而ZGC和Shenandoah的成熟为低延迟应用提供了更多选择。Unicode 13.0和EdDSA支持进一步增强了Java的国际化能力和安全性。
开发者应尝试这些新特性,尤其是在测试环境中探索预览和孵化特性。使用预览特性时,记得启用--enable-preview
标志;使用孵化特性时,需启用相应模块。未来版本可能会对这些特性进行调整或正式化,因此保持关注是明智的选择。
参考资料
- JDK 15 Release Notes
- Java 15 Features on javaalmanac.io
- OpenJDK JEP Index
- Java Language Updates