作为一名长期从事Java开发的工程师,我见证了Java语言的不断演进。JDK 17作为最新的长期支持版本(LTS),带来了许多令人振奋的语法改进。这些特性不仅让代码更加简洁优雅,还显著提升了开发效率和代码质量。下面我将结合自己多年的实战经验,详细剖析这些新特性的使用场景和最佳实践。
Record是JDK 14引入的一种新型类声明方式,专门用于定义不可变的数据载体类。它完美解决了传统Java中定义纯数据类时代码冗长的问题。
java复制// 传统Java类定义方式
public class User {
private String name;
private String password;
public User() {}
public User(String name, String password) {
this.name = name;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean equals(Object o) {...}
@Override
public int hashCode() {...}
@Override
public String toString() {...}
}
// 使用Record简化后的版本
public record User(String name, String password) {}
自动生成的成员:
不可变性:
简洁性:
Record特别适合作为数据传输对象(DTO)使用。传统DTO需要开发者自觉遵守不变性原则,而Record通过语言特性强制实现了这一点。
java复制// API响应DTO示例
public record ApiResponse<T>(int code, String message, T data) {}
// 使用示例
ApiResponse<User> response = new ApiResponse<>(200, "Success", user);
提示:当API响应结构需要变更时,修改Record定义会立即在编译时暴露出所有需要调整的代码位置,这大大降低了维护成本。
Record可以与Spring Boot完美配合,简化依赖注入代码:
java复制@Service
public record UserService(UserRepository repository) {
public User findById(Long id) {
return repository.findById(id).orElseThrow();
}
}
这种构造器注入方式比传统的@Autowired更加简洁,且不可变性保证了服务类在使用过程中的安全性。
Record与Java 17的模式匹配特性结合使用时尤为强大:
java复制// 定义几何图形Record
public sealed interface Shape permits Circle, Rectangle {
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
}
// 使用模式匹配处理不同图形
double area(Shape shape) {
return switch(shape) {
case Shape.Circle c -> Math.PI * c.radius() * c.radius();
case Shape.Rectangle r -> r.width() * r.height();
};
}
封闭类允许开发者精确控制哪些类可以继承它,解决了传统Java中"要么全开放,要么全封闭"的继承限制问题。
java复制// 定义封闭类
public abstract sealed class Shape permits Circle, Square {
private final String id;
public Shape(String id) {
this.id = id;
}
public abstract double area();
}
// 定义许可子类
public final class Circle extends Shape {
private final double radius;
public Circle(String id, double radius) {
super(id);
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
public non-sealed class Square extends Shape {
private final double side;
public Square(String id, double side) {
super(id);
this.side = side;
}
@Override
public double area() {
return side * side;
}
}
精确的继承控制:
子类修饰符要求:
封闭类特别适合领域驱动设计(DDD)中的领域模型定义:
java复制// 支付领域模型示例
public sealed interface PaymentMethod
permits CreditCard, BankTransfer, DigitalWallet {
record CreditCard(String cardNumber, String expiryDate) implements PaymentMethod {}
record BankTransfer(String accountNumber, String bankCode) implements PaymentMethod {}
record DigitalWallet(String walletId) implements PaymentMethod {}
}
这种设计确保了支付方式的类型安全,防止了不受控制的扩展。
封闭类与switch表达式结合使用时,编译器可以检查是否处理了所有可能的情况:
java复制String processPayment(PaymentMethod method) {
return switch(method) {
case CreditCard cc -> "Processing credit card: " + cc.cardNumber();
case BankTransfer bt -> "Processing bank transfer to: " + bt.accountNumber();
case DigitalWallet dw -> "Processing digital wallet: " + dw.walletId();
};
}
如果新增了PaymentMethod的实现类但未在switch中处理,编译器会报错,这大大提高了代码的健壮性。
在设计公共API时,封闭类可以确保使用者只能使用我们预期的子类:
java复制// 定义API响应类型体系
public sealed interface ApiResult<T> permits Success, Failure {
record Success<T>(T data) implements ApiResult<T> {}
record Failure<T>(String error) implements ApiResult<T> {}
}
// 使用示例
public ApiResult<User> getUserById(Long id) {
try {
User user = repository.findById(id);
return new ApiResult.Success<>(user);
} catch (Exception e) {
return new ApiResult.Failure<>(e.getMessage());
}
}
传统类型检查与转换需要分两步进行,容易出错:
java复制// 传统写法
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
// JDK 16模式匹配写法
if (obj instanceof String s) {
System.out.println(s.length());
}
模式匹配将类型检查和变量声明合并为一个操作,减少了样板代码和潜在错误。
JDK 14引入了全新的switch表达式语法,解决了传统switch语句的多个痛点:
java复制// 传统switch语句
int daysInMonth;
switch (month) {
case Calendar.JANUARY:
case Calendar.MARCH:
case Calendar.MAY:
case Calendar.JULY:
case Calendar.AUGUST:
case Calendar.OCTOBER:
case Calendar.DECEMBER:
daysInMonth = 31;
break;
case Calendar.APRIL:
case Calendar.JUNE:
case Calendar.SEPTEMBER:
case Calendar.NOVEMBER:
daysInMonth = 30;
break;
case Calendar.FEBRUARY:
if (isLeapYear(year)) daysInMonth = 29;
else daysInMonth = 28;
break;
default:
throw new IllegalArgumentException();
}
// 增强的switch表达式
int daysInMonth = switch (month) {
case Calendar.JANUARY, Calendar.MARCH, Calendar.MAY,
Calendar.JULY, Calendar.AUGUST, Calendar.OCTOBER,
Calendar.DECEMBER -> 31;
case Calendar.APRIL, Calendar.JUNE,
Calendar.SEPTEMBER, Calendar.NOVEMBER -> 30;
case Calendar.FEBRUARY -> {
if (isLeapYear(year)) yield 29;
else yield 28;
}
default -> throw new IllegalArgumentException();
};
java复制public enum OrderStatus { NEW, PROCESSING, SHIPPED, DELIVERED, CANCELLED }
String handleOrder(OrderStatus status) {
return switch(status) {
case NEW -> "Order received";
case PROCESSING -> "Preparing for shipment";
case SHIPPED -> "On the way";
case DELIVERED -> "Completed";
case CANCELLED -> "Order cancelled";
};
}
java复制interface Shape { double area(); }
record Circle(double radius) implements Shape { /*...*/ }
record Rectangle(double w, double h) implements Shape { /*...*/ }
double calculateArea(Shape shape) {
return switch(shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.w() * r.h();
};
}
Java模块系统(JPMS)在JDK 9中引入,主要解决两个问题:
java复制// module-info.java
module com.example.myapp {
requires java.base; // 隐式依赖,可不写
requires java.sql;
requires transitive com.example.utils; // 传递性依赖
exports com.example.myapp.api;
exports com.example.myapp.service to com.example.frontend;
opens com.example.myapp.internal to spring.core;
provides com.example.spi.ServiceProvider
with com.example.myapp.MyServiceProvider;
uses com.example.spi.OtherService;
}
requires:声明模块依赖
requires transitive:传递性依赖requires static:可选依赖exports:导出包给其他模块
exports...to:限定导出给特定模块opens:允许反射访问
provides...with:声明服务提供者
uses:声明服务消费者
code复制myapp
├── application
│ └── src
│ └── main
│ └── java
│ └── module-info.java // requires domain, infrastructure
├── domain
│ └── ... // 核心业务逻辑
└── infrastructure
└── ... // 技术实现细节
java复制module com.example.domain {
exports com.example.domain.model;
exports com.example.domain.service;
// 不导出实现包
// com.example.domain.internal 保持隐藏
}
这种设计确保了领域模型的核心实现细节不会被意外依赖。
Spring Boot从2.4版本开始提供对JPMS的更好支持。建议:
opens指令spring-context-indexer替代部分反射JDK 15正式引入的多行字符串语法:
java复制// 传统方式
String html = "<html>\n" +
" <body>\n" +
" <p>Hello, world!</p>\n" +
" </body>\n" +
"</html>\n";
// 文本块方式
String html = """
<html>
<body>
<p>Hello, world!</p>
</body>
</html>
""";
文本块会自动处理以下内容:
JDK 10引入的局部变量类型推断:
java复制// 传统写法
Map<String, List<Employee>> employeesByDept = new HashMap<>();
// 使用var
var employeesByDept = new HashMap<String, List<Employee>>();
使用限制:
java复制List<String> list = List.of("a", "b", "c");
Set<String> set = Set.of("a", "b", "c");
Map<String, Integer> map = Map.of("a", 1, "b", 2);
// 超过10个键值对
Map<String, Integer> bigMap = Map.ofEntries(
entry("a", 1),
entry("b", 2),
// ...
);
这些集合是不可变的,任何修改操作都会抛出UnsupportedOperationException。
java复制// takeWhile/dropWhile
List<Integer> numbers = Stream.of(2,4,6,8,9,10)
.takeWhile(n -> n % 2 == 0) // [2,4,6,8]
.dropWhile(n -> n < 5) // [6,8]
.toList();
// ofNullable
long count = Stream.ofNullable(null).count(); // 0
java复制// isBlank
" ".isBlank(); // true
// lines
"line1\nline2".lines().count(); // 2
// repeat
"Java ".repeat(3); // "Java Java Java "
// indent
"text".indent(2); // " text\n"
java复制public record Credentials(String username, String password) {
// 可以添加方法
public String maskedPassword() {
return "*".repeat(password.length());
}
// 但不能添加实例字段
// private String token; // 编译错误
}
构建工具:
IDE配置:
静态分析:
Java语言仍在快速演进,后续版本值得关注的新特性包括:
作为开发者,我们应该: