什么是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));
}
}
Note: It is over 100 of lines of code just to create a class that carries some data.
现在让我们看看在讨论下面给出的记录的属性之前,使用 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文件夹后,请按照以下步骤操作。
Note: For this tutorial Eclipse IDE is used.
设置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() 方法已由编译器自动生成。