乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

Records 记录类(JDK 14 预览 / JDK 16 正式)

周一早上的架构评审会上,大翔在投影幕上打开了一个 EmployeeDTO.java——197 行代码,只做了一件事:承载数据。

"构造器 15 行,getter 40 行,equals 25 行,hashCode 20 行,toString 15 行……剩下的全是 IDE 生成的样板代码。"大翔滚动着屏幕,"谁写的?"

小崔默默举手:"是我。但这不是标准写法吗?全公司所有 DTO 都这样。"

白歌走到白板前,写下了一行代码:record Employee(String name, int age) {}。

"JDK 16 正式发布的 Records。就这一行,自动生成构造器、getter、equals、hashCode、toString——197 行变 1 行。Lombok 可以卸载了。"


定义表

概念描述
Record(记录类)JDK 16 正式特性,一种特殊的不可变数据载体类,用 record 关键字声明,自动生成全参构造器、访问器方法、equals、hashCode、toString
组件(Component)Record 头部括号中声明的字段,如 record Point(int x, int y) 中的 x 和 y,每个组件自动生成对应的 public 访问器方法
紧凑构造器(Compact Constructor)不含参数列表的构造器,用于参数校验——编译器自动将校验后的值赋给组件字段
访问器方法(Accessor Method)Record 自动生成的方法,名称与组件名相同(如 x() 而非 getX())
不可变性Record 的所有组件字段隐式为 private final,没有 setter,实例创建后不可修改

Record vs 传统 POJO / DTO


基础用法:一行定义数据载体

public class RecordBasic {
    // === JDK 16:一行定义不可变 Point ===
    record Point(int x, int y) {}

    public static void main(String[] args) {
        Point p1 = new Point(10, 20);
        Point p2 = new Point(10, 20);
        Point p3 = new Point(30, 40);

        // 自动生成的 toString()
        System.out.println("p1 = " + p1);

        // 自动生成的访问器方法(注意:是 x() 不是 getX())
        System.out.println("p1.x = " + p1.x() + ", p1.y = " + p1.y());

        // 自动生成的 equals() —— 基于组件值比较
        System.out.println("p1.equals(p2) = " + p1.equals(p2));  // true
        System.out.println("p1.equals(p3) = " + p1.equals(p3));  // false

        // 自动生成的 hashCode()
        System.out.println("p1.hashCode() = " + p1.hashCode());
        System.out.println("p2.hashCode() = " + p2.hashCode());
    }
}

输出:

p1 = Point[x=10, y=20]
p1.x = 10, p1.y = 20
p1.equals(p2) = true
p1.equals(p3) = false
p1.hashCode() = 330
p2.hashCode() = 330

紧凑构造器:优雅的参数校验

传统的构造器参数校验需要写完整的构造方法。Record 的紧凑构造器省略了参数列表和字段赋值语句,编译器自动完成赋值。

public class RecordCompactConstructor {
    // === 紧凑构造器:省略参数列表和 this.x = x ===
    record Employee(String name, int age) {
        // 紧凑构造器 — 编译器自动将校验后的参数赋给字段
        Employee {
            if (name == null || name.isBlank()) {
                throw new IllegalArgumentException("姓名不能为空");
            }
            if (age < 0 || age > 150) {
                throw new IllegalArgumentException("年龄必须在 0-150 之间");
            }
        }

        // 可以添加其他实例方法
        public String nameWithAge() {
            return name + "(" + age + "岁)";
        }
    }

    public static void main(String[] args) {
        // === 正常创建 ===
        Employee e1 = new Employee("大翔", 35);
        System.out.println(e1.nameWithAge());

        // === 校验生效 ===
        try {
            new Employee("", 25);
        } catch (IllegalArgumentException e) {
            System.out.println("空姓名校验通过: " + e.getMessage());
        }

        try {
            new Employee("白歌", -1);
        } catch (IllegalArgumentException e) {
            System.out.println("非法年龄校验通过: " + e.getMessage());
        }

        // === 飞翔科技项目实战:多个 record 组合使用 ===
        record Department(String name, String leader) {}
        record Project(String name, Department dept, Employee leader) {}

        Department tech = new Department("技术部", "白歌");
        Project p = new Project("智能运维平台", tech, e1);
        System.out.println("\n项目信息: " + p);
    }
}

输出:

大翔(35岁)
空姓名校验通过: 姓名不能为空
非法年龄校验通过: 年龄必须在 0-150 之间

项目信息: Project[name=智能运维平台, dept=Department[name=技术部, leader=白歌], leader=Employee[name=大翔, age=35]]

Record 实现接口

Record 可以像普通类一样实现接口,但不能继承父类(Record 隐式继承 java.lang.Record)。

public class RecordInterface {
    // === 定义可序列化的接口 ===
    interface JsonSerializable {
        String toJson();
    }

    // === Record 实现接口 ===
    record ApiResponse(int code, String message, String data) implements JsonSerializable {
        @Override
        public String toJson() {
            return """
                    {
                      "code": %d,
                      "message": "%s",
                      "data": "%s"
                    }""".formatted(code, message, data);
        }
    }

    // === 另一个 Record 实现同一接口 ===
    record ErrorResponse(int code, String error) implements JsonSerializable {
        @Override
        public String toJson() {
            return """
                    {
                      "code": %d,
                      "error": "%s"
                    }""".formatted(code, error);
        }
    }

    public static void main(String[] args) {
        ApiResponse success = new ApiResponse(200, "查询成功",
                "[{\"name\":\"大翔\"},{\"name\":\"白歌\"}]");
        ErrorResponse failure = new ErrorResponse(404, "员工不存在");

        System.out.println("成功响应 JSON:\n" + success.toJson());
        System.out.println("\n错误响应 JSON:\n" + failure.toJson());

        // === 多态:用接口类型统一处理 ===
        JsonSerializable[] responses = { success, failure };
        for (var resp : responses) {
            System.out.println("\n--- 统一处理 ---");
            System.out.println(resp.toJson());
        }
    }
}

输出:

成功响应 JSON:
{
  "code": 200,
  "message": "查询成功",
  "data": "[{\"name\":\"大翔\"},{\"name\":\"白歌\"}]"
}

错误响应 JSON:
{
  "code": 404,
  "error": "员工不存在"
}

--- 统一处理 ---
{
  "code": 200,
  "message": "查询成功",
  "data": "[{\"name\":\"大翔\"},{\"name\":\"白歌\"}]"
}

--- 统一处理 ---
{
  "code": 404,
  "error": "员工不存在"
}

Record 的静态方法与字段

Record 允许添加静态字段和静态方法,类似于普通类。

import java.util.List;

public class RecordStaticMembers {
    record Employee(String name, String department, int salary) {
        // === 静态字段(允许)===
        private static final String COMPANY = "飞翔科技";
        private static final int DEFAULT_SALARY = 10000;

        // === 静态工厂方法 ===
        public static Employee intern(String name, String department) {
            return new Employee(name, department, DEFAULT_SALARY);
        }

        // === 静态工具方法 ===
        public static List<Employee> filterByDept(List<Employee> employees, String dept) {
            return employees.stream()
                    .filter(e -> e.department.equals(dept))
                    .toList();
        }

        // === 实例方法:展示信息 ===
        public String info() {
            return "%s | %s | %s | %d元/月".formatted(COMPANY, name, department, salary);
        }
    }

    public static void main(String[] args) {
        // === 静态工厂方法创建 ===
        Employee intern = Employee.intern("实习生小明", "技术部");
        System.out.println("实习生: " + intern.info());

        // === 正常构造 ===
        List<Employee> all = List.of(
                new Employee("大翔", "技术部", 35000),
                new Employee("白歌", "技术部", 30000),
                new Employee("Frank", "市场部", 28000),
                new Employee("小崔", "技术部", 12000)
        );

        System.out.println("\n技术部员工:");
        Employee.filterByDept(all, "技术部")
                .forEach(e -> System.out.println("  " + e.info()));
    }
}

输出:

实习生: 飞翔科技 | 实习生小明 | 技术部 | 10000元/月

技术部员工:
  飞翔科技 | 大翔 | 技术部 | 35000元/月
  飞翔科技 | 白歌 | 技术部 | 30000元/月
  飞翔科技 | 小崔 | 技术部 | 12000元/月

易错场景

Record 不能继承类,也不能被继承

public class RecordInheritancePitfall {
    // Record 隐式继承 java.lang.Record,且 Record 是 final 的
    record Point(int x, int y) {}

    public static void main(String[] args) {
        // ❌ 编译错误:Record 不能继承其他类
        // record Point3D(int x, int y, int z) extends Point {}  // 错误

        // ❌ 编译错误:其他类不能继承 Record
        // class ColoredPoint extends Point {}  // 错误

        // ✅ Record 可以像 final 类一样通过组合复用
        record Point3D(Point point, int z) {
            public int x() { return point.x(); }
            public int y() { return point.y(); }
        }

        Point3D p3d = new Point3D(new Point(10, 20), 30);
        System.out.println("3D 点: " + p3d);
        System.out.println("x=" + p3d.x() + ", y=" + p3d.y() + ", z=" + p3d.z());
    }
}

输出:

3D 点: Point3D[point=Point[x=10, y=20], z=30]
x=10, y=20, z=30

不能声明非静态字段

public class RecordNonStaticFieldPitfall {
    record Employee(String name, int age) {
        // ❌ 编译错误:Record 不能有实例字段(除组件外的非静态字段)
        // private String nickname;  // 错误

        // ✅ 可以定义静态字段
        private static final String COMPANY = "飞翔科技";

        // ✅ 可以定义实例方法
        public String greeting() {
            return "%s员工 %s,%d岁".formatted(COMPANY, name, age);
        }
    }

    public static void main(String[] args) {
        Employee e = new Employee("大翔", 35);
        System.out.println(e.greeting());
    }
}

输出:

飞翔科技员工 大翔,35岁

不能修改 Record 组件值

public class RecordImmutabilityPitfall {
    record Mutable(int value) {}

    public static void main(String[] args) {
        Mutable m = new Mutable(42);
        System.out.println("初始值: " + m.value());

        // ❌ Record 没有 setter,也不能修改字段
        // m.value = 100;  // 编译错误:组件字段是 final 的

        // ✅ 创建新实例来"修改"
        // 但 Record 没有自动的 with 方法,需要手动构造
        Mutable m2 = new Mutable(100);
        System.out.println("新实例值: " + m2.value());
    }
}

输出:

初始值: 42
新实例值: 100

面试考点

面试官常问的三个问题:

问题一:"Record 和 Lombok @Data 有什么区别?选哪个?"

答案:核心区别在于语言级支持 vs 第三方注解处理器。Record 是 Java 语言原生特性,编译器直接识别 record 关键字并生成字节码,不依赖任何外部工具。Record 强制不可变性(所有字段 final,无 setter),语义更清晰——"这个类就是一个数据载体"。Lombok @Data 则生成可变类(含 setter),依赖注解处理器和 IDE 插件。在新项目中建议优先使用 Record 替代不可变 DTO;如果需要可变性,仍可使用普通类或 Lombok。

问题二:"Record 的 equals() 和 hashCode() 是怎么实现的?"

答案:编译器自动生成基于所有组件的 equals() 和 hashCode()。equals() 使用 instanceof 检查类型,然后逐一比较每个组件(基本类型用 ==,引用类型用 Objects.equals())。hashCode() 基于所有组件的哈希值组合计算。这与 IDE 自动生成的实现逻辑基本一致,但 Record 保证了一致性——你不需要担心生成代码被手动修改导致 equals/hashCode 不一致。

问题三:"紧凑构造器和普通构造器有什么区别?什么时候用?"

答案:紧凑构造器省略了参数列表和字段赋值,专注于参数校验和规范化。语法上只需写 RecordName { ... },编译器自动将参数赋给组件字段。关键规则:在紧凑构造器中对参数名的赋值会在构造器退出时自动写回组件字段。适用于需要校验参数合法性的场景——如非空检查、范围检查、防御性拷贝(引用类型组件)。如果不需要校验,完全不需要写任何构造器,编译器生成的全参构造器即可。


大翔在周报中写道:"本周技术部完成了 DTO 层的 Records 迁移——项目中的 42 个 DTO 类从平均 85 行缩减到平均 12 行,删除了 3066 行样板代码。白歌说得对——Records 不是 Lombok 的替代品,它代表的是 Java 语言对'数据即数据'这一理念的正式认可。"

上一页
文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
下一页
密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)