📜  什么是Java记录以及如何将它们与构造函数和方法一起使用?

📅  最后修改于: 2022-05-13 01:55:25.686000             🧑  作者: Mango

什么是Java记录以及如何将它们与构造函数和方法一起使用?

作为开发人员和软件工程师,我们的目标是始终设计获得最大效率的方法,如果我们需要为它编写更少的代码,那就是一种祝福。

在Java,记录是一种特殊类型的类声明,旨在减少样板代码。引入Java记录的目的是用作创建数据载体类的快速方法,即目标是简单地包含数据并在模块之间携带数据的类,也称为 POJO(Plain Old Java Objects)和 DTO(Data传输对象)。 Record 是在Java SE 14 中作为预览功能引入的,该功能的设计、实现和规范是完整的,但它不是语言的永久添加,这意味着该功能可能会或可能不会在未来版本中存在的语言。 Java SE 15 使用本地记录类等附加功能扩展了预览功能。

让我们首先讨论为什么在实施之前需要记录。让我们考虑一个例子。

插图:

考虑一个简单的类 Employee,其目标是包含员工的数据,例如其 ID 和姓名,并充当跨模块传输的数据载体。要创建这样一个简单的类,您需要定义其构造函数、getter 和 setter 方法,并且如果您想使用具有 HashMap 等数据结构的对象或将其对象的内容打印为字符串,我们需要覆盖方法,例如 equals()、hashCode() 和 toString()。



例子

Java
// Java Program Illustrating Program Withoput usage of Records 
  
// A sample Employee class
class Employee {
  
    // Member variables of this class
    private String firstName;
    private String lastName;
    private int Id;
  
    // Constructor of this class
    public Employee(String firstName, String lastName,
                    int Id)
    {
  
        // This keyword refers to current instance itself
        this.firstName = firstName;
        this.lastName = lastName;
        this.Id = Id;
    }
  
    // Setter and Getter methods
  
    // Setter-getter Method 1
    public void setFirstName(String firstName)
    {
        this.firstName = firstName;
    }
  
    // Setter-getter Method 2
    // to get the first name of employee
    public String getFirstName() { return firstName; }
  
    // Setter-getter Method 3
    // To set  the last name of employees
    public void setLastName(String lasstName)
    {
  
        // This keyword refers to current object itself
        this.lastName = lastName;
    }
  
    // Setter-getter Method 3
    // To set  the last name of employees
    public String getLastName() { return lastName; }
  
    // Setter-getter Method 4
    // To set  the last name of employees
    public void setId(int Id) { this.Id = Id; }
  
    // Setter-getter Method 5
    // To set  the last name of employees
    public int getId() { return Id; }
  
    // Setter-getter Method 6
    public String toString()
    {
  
        // Return the attributes
        return "Employee [firstName=" + firstName
            + ", lastName=" + lastName + ", Id=" + Id + "]";
    }
  
    // Method 7
    // Overriding hashCode method
    @Override public int hashCode()
    {
  
        // Final variable
        final int prime = 31;
        int result = 1;
  
        result = prime * result + Id;
        result = prime * result
                 + ((firstName == null)
                        ? 0
                        : firstName.hashCode());
        result
            = prime * result
              + ((lastName == null) ? 0
                                    : lastName.hashCode());
  
        return result;
    }
  
    // Method 8
    // Overriding equals method to
    // implement with data structures
    @Override public boolean equals(Object obj)
    {
  
        // This refers to current instance itself
        if (this == obj)
            return true;
  
        if (obj == null)
            return false;
  
        if (getClass() != obj.getClass())
            return false;
  
        Employee other = (Employee)obj;
  
        if (Id != other.Id)
            return false;
  
        if (firstName == null) {
            if (other.firstName != null)
                return false;
        }
  
        else if (!firstName.equals(other.firstName))
            return false;
  
        if (lastName == null) {
            if (other.lastName != null)
                return false;
        }
  
        else if (!lastName.equals(other.lastName))
            return false;
  
        return true;
    }
}


Java
// Java Program to Illustrate Record's functionalities
  
// Main class
class GFG {
  
  // Main driver method
  public static void main(String args[]) {
  
    // Creating object with default constructor
    Employee e1 = new Employee(1001, "Derok", "Dranf");
  
    // auto generated getter methods
    System.out.println(e1.id() + " " + e1.firstName()
                       + " " + e1.lastName());
  
    // Auto-generated toString() method
    System.out.println(e1.toString());
  
    // Creating object with parameterised constructor
    Employee e2 = new Employee(1002, "Seren");
  
    // Using instance methods
    e2.getFullName();
  
    // Using static methods
    System.out.println("Employee " + e2.id()
                       + " Token = "
                       + e2.generateEmployeeToken());
  
    // Using the equals() method
    System.out.print("Is e1 equal to e2: "
                     + e1.equals(e2));
  }
}


现在让我们看看在讨论下面给出的记录的属性之前,使用 Record 创建一个类似的类来获取它的用法需要什么:

记录的更多属性

  • 您可以在记录中使用嵌套的类和接口。
  • 您也可以有嵌套记录,这将隐式地是静态的。
  • 记录可以实现接口。
  • 您可以创建通用记录类。
  • 可以使用本地记录类(自Java SE 15 起)。
  • 记录是可序列化的。

尽管将记录用于数据载体对象可能很诱人,但记录仍然是Java的预览功能。此外,由于它们仅用作数据的载体,因此定义我们自己的访问方法和其他实例方法将违背其目的。记录可用于减少开发人员所做的工作,但在内部记录和类之间的性能差异并没有那么大

public record Employee(int id, String firstName, String lastName) {}

就是这样!只需 2 行代码,这就是使用 Record 实现这 80 行代码所需的全部内容。要了解Java如何实现这样的功能,我们将首先学习如何自己设置它。现在让我们讨论带有演示Java记录的可视化辅助工具的步骤。由于 Records 是Java SE 14 的一个特性,我们需要在我们的机器上安装 JDK 14。从此存档中为您的机器下载 Oracle JDK 14。将 JDK-14 与任何其他Java版本一起下载并安装到Java文件夹后,请按照以下步骤操作。

设置Java记录的步骤

第一步:新建一个Java项目,选择JavaSE-14作为执行环境。

第 2 步:如果这是您第一次使用 JDK-14,那么您需要执行更多步骤才能配置记录工作。您可能会在项目文件夹中看到此类异常标记。

第 3 步:要解决这个问题,在顶部,转到Window -> Preferences

第 4 步:在 Preferences 窗口中,单击Installed JREs ,然后单击Add,如下所示:

第 5 步:现在在打开的添加 JRE 窗口中,选择标准 VM ,然后单击下一步。您将看到一个新窗口打开以选择 JRE,现在单击目录并导航到安装 jdk-14 的位置并选择该文件夹。单击完成



第 6 步:勾选您刚刚添加的 JDK-14 并应用它。

第 7 步:我们还没有完成。由于记录是一个预览功能,我们需要让他们使用它。在左侧的Project Explorer窗口中,选择您的项目并右键单击并转到其Properties

第 8 步:在打开的窗口右侧,从各种选项中选择Java编译器。之后,在左侧,取消选中图像中用红色箭头标记的设置,并选中用绿色突出显示的设置。这样做将启用预览功能。

第九步:点击Apply and Close后,你会看到一个提示,询问你是否要重建项目。选择

执行:

配置环境后,我们现在可以继续编写记录代码。

编码记录是通过写记录来声明的 在类声明代替。在定义记录时,所有实例字段都作为参数写入。构造函数、getter 方法、toString()、equals() 和 hashCode() 由Java编译器在编译时生成。这里要注意的一件事是记录不提供 setter 方法,因为预期在创建对象时提供实例变量的值。

// A simple Employee class to be used as a DTO

public record Employee(int id, String firstName,
                       String lastName) {
}

示例 1



// Creating Employee object and showcasing its use cases

// Main class
class GFG {

  // Main driver method
  public static void main(String args[]) {

    // Creating object with default constructor
    Employee e1 = new Employee(1001, "Derok", "Dranf");

    // Auto generated getter methods
    System.out.println(e1.id() + " " + e1.firstName()
                       + " " + e1.lastName());

    // Auto-generated toString() method
    System.out.println(e1.toString());
  }
}

输出:

1001 Derok Dranf
Employee[id=1001, firstName=Derok, lastName=Dranf]

我们会注意到 getter 方法在命名约定上与创建的普通 getter 方法不同(例如:getFirstName()),而是简单地由字段名称表示(例如:firstName())。现在让我们扩展之前的示例来测试这些功能。

记录能做的还不止这些。记录还使我们能够:

  • 创建我们自己的构造函数。在记录中,您可以创建一个参数化构造函数,该构造函数使用其主体内提供的参数调用默认构造函数。您还可以创建类似于默认构造函数的紧凑构造函数,但可以添加一些额外的功能,例如在构造函数体内进行检查。
  • 创建实例方法。与任何其他类一样,您可以为记录类创建和调用实例方法。
  • 创建静态字段。记录限制我们只能将实例变量写为参数,但允许使用静态变量和静态方法。

示例 1

// Java Program Illustrating a Record class
// defining constructors, instance methods
// and static fields

// Record class
public record Employee(int id, String firstName,
                       String lastName)
{

    // Instance fields need to be present in the record's
    // parameters but record can define static fields.
    static int empToken;

    // Constructo. 1 of this class
    // Compact Constructor
    public Employee
    {
        if (id < 100) {
            throw new IllegalArgumentException(
                "Employee Id cannot be below 100.");
        }
        if (firstName.length() < 2) {
            throw new IllegalArgumentException(
                "First name must be 2 characters or more.");
        }
    }

    // Constructor 2 of this class
    // Alternative Constructor
    public Employee(int id, String firstName)
    {
        this(id, firstName, null);
    }

    // Instance methods
    public void getFullName()
    {
        if (lastName == null)
            System.out.println(firstName());

        else
            System.out.println(firstName() + " "
                               + lastName());
    }

    // Static methods
    public static int generateEmployeeToken()
    {
        return ++empToken;
    }
}

示例 2

Java

// Java Program to Illustrate Record's functionalities
  
// Main class
class GFG {
  
  // Main driver method
  public static void main(String args[]) {
  
    // Creating object with default constructor
    Employee e1 = new Employee(1001, "Derok", "Dranf");
  
    // auto generated getter methods
    System.out.println(e1.id() + " " + e1.firstName()
                       + " " + e1.lastName());
  
    // Auto-generated toString() method
    System.out.println(e1.toString());
  
    // Creating object with parameterised constructor
    Employee e2 = new Employee(1002, "Seren");
  
    // Using instance methods
    e2.getFullName();
  
    // Using static methods
    System.out.println("Employee " + e2.id()
                       + " Token = "
                       + e2.generateEmployeeToken());
  
    // Using the equals() method
    System.out.print("Is e1 equal to e2: "
                     + e1.equals(e2));
  }
}

输出:

1001 Derok Dranf
Employee[id=1001, firstName=Derok, lastName=Dranf]
Seren
Employee 1002 Token = 1
Is e1 equal to e2: false

Geek,你有没有想过 Compiler 有什么魔力?

如上所述,记录只是一个类的特殊声明,编译器在内部将其转换为具有一些限制的普通类,这使其不同于典型类。当Java文件被Java编译器编译为字节码时,生成的 .class 文件包含记录类的扩展声明。通过查看该文件,我们可以了解更多有关记录的信息。为我们上面创建的 Employee 记录生成的字节码如下:

public final class Employee extends java.lang.Record {
   private final int id;
   private final java.lang.String firstName;
   private final java.lang.String lastName;
   static int empToken;

   public Employee(int id, java.lang.String firstName, java.lang.String lastName) { /* compiled code */ }

   public Employee(int id, java.lang.String firstName) { /* compiled code */ }

   public void getFullName() { /* compiled code */ }

   public static int generateEmployeeToken() { /* compiled code */ }

   public int id() { /* compiled code */ }

   public java.lang.String firstName() { /* compiled code */ }

   public java.lang.String lastName() { /* compiled code */ }

   public java.lang.String toString() { /* compiled code */ }

   public final int hashCode() { /* compiled code */ }

   public final boolean equals(java.lang.Object o) { /* compiled code */ }
}

结论:如果我们花一些时间观察字节码,您会注意到以下内容:

  • 记录已被class取代。
  • 类及其数据成员已声明为final 。这意味着这个类不能被扩展,即不能被继承,并且也是不可变的。
  • 该类扩展了Java.lang.Record 。这意味着所有记录都是在Java.lang 包中定义的 Record 的子类。
  • 有一个默认构造函数和一个参数化构造函数。您会注意到我们定义的紧凑构造函数没有单独的声明。这是因为紧凑构造函数不会生成单独的构造函数,而是将其代码添加到默认构造函数主体的开头。
  • 实例和静态方法按原样声明。
  • toString()、hashCode() 和 equals() 方法已由编译器自动生成。