Records、Sealed Classes这些新特性:Java真的变简单了吗?

在这里插入图片描述

Java这几年新特性挺多的,Records、Sealed Classes、Pattern Matching等等。很多人说Java变简单了,但也有人觉得更复杂了。作为一个一直在用Java的程序员,今天就来聊聊这些新特性,看看是不是真的让Java变简单了。

Records:数据类的简化

Records是Java 14引入的,用来简化数据类。以前写个数据类要写一堆样板代码,现在简单多了。

以前怎么写

// 传统方式:写一堆样板代码
public class User {
    private final String name;
    private final int age;
    private final String email;
    
    public User(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 o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age &&
               Objects.equals(name, user.name) &&
               Objects.equals(email, user.email);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age, email);
    }
    
    @Override
    public String toString() {
        return "User{" +
               "name='" + name + '\'' +
               ", age=" + age +
               ", email='" + email + '\'' +
               '}';
    }
}

写个简单的数据类,要写50多行代码,大部分都是样板代码。

现在用Records

// Records:一行搞定
public record User(String name, int age, String email) {
}

就这么简单!编译器会自动生成:

  • 构造函数
  • getter方法(注意,不是getName(),而是name()
  • equals()hashCode()
  • toString()

Records的实际应用

DTO/VO:

// API响应
public record ApiResponse<T>(int code, String message, T data) {
}

// 使用
ApiResponse<User> response = new ApiResponse<>(200, "success", user);

配置类:

public record DatabaseConfig(
    String url,
    String username,
    String password,
    int maxConnections
) {
}

// 从配置文件加载
@ConfigurationProperties(prefix = "db")
public record DatabaseConfig(
    String url,
    String username,
    String password,
    int maxConnections
) {
}

临时数据结构:

// 方法返回多个值
public record Pair<T, U>(T first, U second) {
}

public Pair<String, Integer> parseUser(String input) {
    String[] parts = input.split(",");
    return new Pair<>(parts[0], Integer.parseInt(parts[1]));
}

Records的局限性

Records也不是万能的,有些限制:

  1. 不可变:所有字段都是final的,不能修改。如果需要可变,还是用普通类。

  2. 不能继承:Records不能继承其他类(除了Record),但可以实现接口。

  3. getter命名:getter方法名是name()而不是getName(),可能和现有代码不兼容。

public record User(String name, int age) {
    // 可以添加自定义方法
    public boolean isAdult() {
        return age >= 18;
    }
    
    // 可以自定义构造函数
    public User {
        if (age < 0) {
            throw new IllegalArgumentException("Age must be positive");
        }
    }
    
    // 可以自定义getter(虽然不推荐)
    public String name() {
        return name.toUpperCase(); // 但这样会递归调用,编译错误
    }
}

我的感受

Records确实让写数据类简单多了,特别是DTO、配置类这些场景。但也不是所有地方都适合用Records,比如需要继承、需要可变性的场景,还是用普通类。

总的来说,Records是个好特性,但不是革命性的,主要是减少样板代码。

Sealed Classes:受控的继承

Sealed Classes是Java 17引入的,用来控制类的继承。以前Java的继承是完全开放的,任何类都可以继承。现在可以限制哪些类能继承。

为什么需要Sealed Classes?

以前写代码,特别是用多态的时候,总担心有子类没处理到:

// 传统方式:完全开放的继承
public abstract class Shape {
    public abstract double area();
}

// 问题:任何人都可以继承Shape,可能有未知的子类
public class Circle extends Shape { }
public class Rectangle extends Shape { }
// 可能还有Triangle、Square等等,编译时不知道所有子类

用switch的时候,编译器不知道所有可能的情况:

public double calculateArea(Shape shape) {
    if (shape instanceof Circle) {
        return ((Circle) shape).getArea();
    } else if (shape instanceof Rectangle) {
        return ((Rectangle) shape).getArea();
    }
    // 问题:可能还有其他子类,但编译器不知道,不会警告
    return 0;
}

Sealed Classes的解决方案

// Sealed Class:限制继承
public sealed class Shape 
    permits Circle, Rectangle, Triangle {
    public abstract double area();
}

// 只能这三个类继承
public final class Circle extends Shape {
    private final double radius;
    
    public Circle(double radius) {
        this.radius = radius;
    }
    
    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

public final class Rectangle extends Shape {
    private final double width;
    private final double height;
    
    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
    
    @Override
    public double area() {
        return width * height;
    }
}

public final class Triangle extends Shape {
    // ...
}

现在编译器知道所有可能的子类了,用Pattern Matching的时候会有检查:

// Pattern Matching for switch(Java 21+)
public double calculateArea(Shape shape) {
    return switch (shape) {
        case Circle c -> Math.PI * c.radius() * c.radius();
        case Rectangle r -> r.width() * r.height();
        case Triangle t -> t.base() * t.height() / 2;
        // 编译器知道所有情况都处理了,不需要default
    };
}

如果漏了某个子类,编译器会报错,这样就安全多了。

Sealed Classes的实际应用

状态机:

public sealed interface OrderState 
    permits OrderPending, OrderPaid, OrderShipped, OrderDelivered, OrderCancelled {
}

public final class OrderPending implements OrderState { }
public final class OrderPaid implements OrderState { }
public final class OrderShipped implements OrderState { }
public final class OrderDelivered implements OrderState { }
public final class OrderCancelled implements OrderState { }

表达式树:

public sealed interface Expr 
    permits ConstantExpr, AddExpr, MultiplyExpr {
}

public record ConstantExpr(int value) implements Expr { }
public record AddExpr(Expr left, Expr right) implements Expr { }
public record MultiplyExpr(Expr left, Expr right) implements Expr { }

我的感受

Sealed Classes是个很好的特性,特别是和Pattern Matching配合使用。它让代码更安全,编译器可以检查完整性。

但这个特性可能对大部分开发者来说,用处不大。主要是框架开发者、库开发者用得比较多。普通业务开发,可能用不到。

Pattern Matching:简化类型判断

Pattern Matching是Java 16引入的,用来简化instanceof的使用。

以前怎么写

// 传统方式:繁琐的类型判断和转换
if (obj instanceof String) {
    String str = (String) obj;
    System.out.println(str.length());
}

现在用Pattern Matching

// Pattern Matching:自动类型转换
if (obj instanceof String str) {
    System.out.println(str.length()); // str已经自动转换了
}

Pattern Matching for switch

Java 21支持在switch中用Pattern Matching:

// 传统方式
public String format(Object obj) {
    if (obj instanceof Integer i) {
        return String.format("Integer: %d", i);
    } else if (obj instanceof String s) {
        return String.format("String: %s", s);
    } else if (obj instanceof Double d) {
        return String.format("Double: %f", d);
    } else {
        return "Unknown";
    }
}

// Pattern Matching for switch
public String format(Object obj) {
    return switch (obj) {
        case Integer i -> String.format("Integer: %d", i);
        case String s -> String.format("String: %s", s);
        case Double d -> String.format("Double: %f", d);
        default -> "Unknown";
    };
}

配合Sealed Classes使用

public double calculateArea(Shape shape) {
    return switch (shape) {
        case Circle(double radius) -> Math.PI * radius * radius; // 直接解构
        case Rectangle(double w, double h) -> w * h;
        case Triangle(double b, double h) -> b * h / 2;
    };
}

我的感受

Pattern Matching确实让代码更简洁,特别是和Sealed Classes配合使用的时候。但单独用的话,提升没那么明显。

总结:Java真的变简单了吗?

这些新特性确实让Java在某些场景下更简洁了:

  • Records减少了样板代码
  • Sealed Classes + Pattern Matching让代码更安全
  • Pattern Matching简化了类型判断

但Java并没有真正"变简单",而是:

  1. 更精确:可以表达更精确的语义
  2. 更安全:编译器可以做更多检查
  3. 更简洁:减少了样板代码

对于新手来说,这些特性可能增加了学习成本。对于老手来说,这些特性让写代码更舒服了。

我的建议是:

  • Records:可以多用,特别是DTO、配置类
  • Sealed Classes:看场景,框架开发、库开发用得比较多
  • Pattern Matching:配合Sealed Classes用,效果最好

不要为了用新特性而用,要根据实际需求来选择。

更多推荐