在Java的世界里,数据类是我们经常会用到的一种类,它主要用于存储数据。传统的数据类往往需要编写大量的样板代码,比如构造函数、getter和setter方法、equals()hashCode()以及toString()方法等。而JDK 17中引入的Record类,就像是一把神奇的钥匙,为我们打开了简洁编写数据类的大门。今天,我们就来深入学习Record类,看看它是如何作为简洁的数据载体发挥作用的。

Record类的定义和使用

什么是Record类

Record类是Java 16中引入的一种特殊类,在JDK 17中得到了进一步的完善和应用。简单来说,Record类是一种不可变的数据类,它可以自动生成构造函数、getter方法、equals()hashCode()toString()方法,大大减少了我们编写样板代码的工作量。

用大白话来讲,就好比我们以前要盖一座房子,需要自己一块砖一块砖地垒,还要自己安装门窗、铺设地板等,非常麻烦。而现在有了Record类,就像是有了一个预制房屋,很多结构和设施都已经帮我们做好了,我们只需要稍微布置一下就可以入住了。

定义Record类

下面我们通过一个简单的例子来看看如何定义一个Record类。假设我们要定义一个表示学生的类,包含姓名和年龄两个属性。

// 定义一个Record类
record Student(String name, int age) {
}

在这个例子中,我们使用record关键字定义了一个名为Student的Record类,括号内的String nameint age是这个Record类的组件(类似于普通类的成员变量)。

使用Record类

定义好Record类后,我们就可以使用它了。下面是一个使用Student类的示例:

public class RecordExample {
    public static void main(String[] args) {
        // 创建一个Student对象
        Student student = new Student("Alice", 20);

        // 获取学生的姓名和年龄
        String name = student.name();
        int age = student.age();

        System.out.println("Name: " + name + ", Age: " + age);
        System.out.println(student);
    }
}

在这个示例中,我们创建了一个Student对象,并通过自动生成的访问器方法(name()age())获取了学生的姓名和年龄。同时,我们还直接打印了student对象,这时候会调用自动生成的toString()方法,输出对象的信息。

Record类的构造和使用

构造Record类

Record类的构造函数是自动生成的,它会接收所有组件作为参数。我们也可以自定义构造函数,但需要注意的是,自定义构造函数必须调用自动生成的构造函数(也称为规范构造函数)。

下面是一个自定义构造函数的示例:

record Student(String name, int age) {
    // 自定义构造函数
    public Student {
        if (age < 0) {
            throw new IllegalArgumentException("Age cannot be negative");
        }

        // 调用规范构造函数
        this(name, age);
    }
}

在这个示例中,我们自定义了一个构造函数,用于检查年龄是否为负数。如果年龄为负数,会抛出一个异常。

使用Record类进行数据传递

Record类非常适合作为数据载体,用于在不同的模块或方法之间传递数据。下面是一个简单的示例:

public class RecordDataTransfer {
    public static void main(String[] args) {
        Student student = new Student("Bob", 22);
        printStudentInfo(student);
    }

    public static void printStudentInfo(Student student) {
        System.out.println("Student Info: " + student);
    }
}

在这个示例中,我们将Student对象作为参数传递给printStudentInfo方法,用于打印学生的信息。

Record类与普通类的区别

代码简洁性

普通类需要手动编写构造函数、getter和setter方法、equals()hashCode()toString()方法等,代码量比较大。而Record类只需要定义组件,这些方法都会自动生成,代码非常简洁。

下面是一个普通类的示例:

public class NormalStudent {
    private final String name;
    private final int age;

    public NormalStudent(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        NormalStudent that = (NormalStudent) o;
        return age == that.age && name.equals(that.name);
    }

    @Override
    public int hashCode() {
        return java.util.Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "NormalStudent{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

可以看到,普通类的代码量明显比Record类多很多。

不可变性

Record类是不可变的,它的组件一旦初始化就不能再修改。而普通类可以通过setter方法修改成员变量的值。

自动生成方法

Record类会自动生成构造函数、getter方法、equals()hashCode()toString()方法,而普通类需要手动编写这些方法。

Record类在序列化和反序列化中的问题及解决方法

序列化和反序列化的概念

序列化是指将对象转换为字节流的过程,反序列化则是将字节流转换为对象的过程。在Java中,对象要想实现序列化和反序列化,需要实现Serializable接口。

Record类的序列化和反序列化

Record类默认是可序列化的,因为它隐式地实现了Serializable接口。下面是一个Record类序列化和反序列化的示例:

import java.io.*;

record Student(String name, int age) implements Serializable {
}

public class RecordSerialization {
    public static void main(String[] args) {
        Student student = new Student("Charlie", 22);

        try {
            // 序列化
            FileOutputStream fileOut = new FileOutputStream("student.ser");
            ObjectOutputStream out = new ObjectOutputStream(fileOut);
            out.writeObject(student);
            out.close();
            fileOut.close();

            // 反序列化
            FileInputStream fileIn = new FileInputStream("student.ser");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            Student deserializedStudent = (Student) in.readObject();
            in.close();
            fileIn.close();

            System.out.println("Deserialized Student: " + deserializedStudent);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,我们将Student对象序列化到文件中,然后再从文件中反序列化出对象。

解决可能出现的问题

虽然Record类默认是可序列化的,但在某些情况下,可能会出现一些问题。比如,当Record类的组件包含不可序列化的对象时,就会抛出NotSerializableException异常。

为了解决这个问题,我们可以将不可序列化的组件标记为transient,这样在序列化时会忽略这些组件。

import java.io.*;

record Student(String name, int age, transient Object nonSerializableObject) implements Serializable {
}

在这个示例中,nonSerializableObject被标记为transient,在序列化时会被忽略。

总结

通过学习Record类,我们掌握了一种简洁编写数据类的方法。Record类可以自动生成构造函数、getter方法、equals()hashCode()toString()方法,大大减少了样板代码的编写。同时,Record类是不可变的,非常适合作为数据载体。在序列化和反序列化方面,Record类默认是可序列化的,但需要注意处理不可序列化的组件。

掌握了Record类的内容后,下一节我们将深入学习JDK 17的其他新语法特性,进一步完善对本章JDK 17新语法特性实战主题的认知。


🍃 程序员JDK17修炼手册系列专栏导航

建议按系列顺序阅读,从基础到进阶逐步掌握JDK核心能力,避免遗漏关键知识点~

系列文章衔接

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐