带有示例的 Java 记录类型

位置:首页>文章>详情   分类: Java教程 > 编程技术   阅读(382)   2023-06-26 07:54:18

了解 Java 中的记录类型。它在 Java 14 中作为预览功能引入,并在稍后完成。 Java 记录应该用作不可变的 POJO,以在类和应用程序之间传输数据。

1. 什么是记录?

张举一样,record也是Java中一种特殊的类类型。它旨在用于创建类仅用作通用数据加载体的地方。

The important 类和记录之间的区别在于记录旨在消除设置实例和从实例获取数据所需的所有样板代码 (JEP-395). Records transfer this responsibility to the Java compiler, which generates the constructor, field getters, 哈希码()and 等于() as well toString() methods.

请注意,我们可以在 记录 定义中覆盖上面提供的任何默认方法以实现自定义行为。

2. 如何创建记录?

我们需要使用关键字记录在Java中创建这样的记录类。就像我们在构造函数中所做的那样,我们需要在记录中提及属性及其类型。在给定的示例中,员工 是一个记录 类型,用于保存员工信息:

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

要创建记录,我们调用其构造函数并传递其中的所有字段信息。然后我们可以使用 JVM 生成的 getter 方法获取记录信息并调用任何生成的方法。

Employee e = new Employee(1l, "Lokesh", "Gupta", "cundage@gmail.com", 38);

System.out.println(e.id());     //1
System.out.println(e.email());  //cundage@gmail.com
System.out.println(e); //Employee[id=1, firstName=Lokesh, lastName=Gupta, email=cundage@gmail.com, age=38]

在上面的示例中,当我们创建 员工 记录时,编译器创建字节代码并在生成的类文件中包含以下内容:

  • 接受所有字段的全参数构造函数。
  • toString() 方法打印记录中所有字段的状态/值。
  • The 等于() and 哈希码() methods using an 动态调用 based mechanism.
  • The getter methods whose names are similar to field names without the usual POJO/JavaBean 得到 prefix i.e. ID(), 名(), 姓(), 电子邮件() and 年龄().
  • 扩展 java.lang.Record,它是所有记录的基类。这意味着记录不能扩展其他类。
  • 该类被标记为最后,因此我们无法创建子类。
  • 它没有任何设置方法,这意味着记录实例被设计为不变的

如果我们在生成的类文件上运行 javap 工具,我们将看到类文件:

public final class com.cundage.core.basic.Employee extends java.lang.Record {
  //1
  public com.cundage.core.basic.Employee(java.lang.Long, java.lang.String, java.lang.String, java.lang.String, int);

  //2
  public java.lang.String toString();

  //3
  public final int hashCode();
  public final boolean equals(java.lang.Object);

  //4
  public java.lang.Long id();
  public java.lang.String firstName();
  public java.lang.String lastName();
  public java.lang.String email();
  public int age();
}

3.规范的、紧凑的和自定义的构造函数

JVM 默认为我们提供了一个全参数构造函数,它将其参数分配给相应的字段。它被称为典型构造函数。与访问器方法一样,我们可以覆盖此构造函数并添加自定义逻辑,例如数据验证。它有助于在给定的业务环境中建立有效记录。

public record Employee(Long id, String firstName, String lastName, String email, int age) {

  public Employee(Long id, String firstName, String lastName, String email, int age) {
    Objects.requireNonNull(id);
    Objects.requireNonNull(email);

    if (age < 18) {
      throw new IllegalArgumentException("You cannot hire a minor as employee");
    }
  }

  //Other methods
}

规范构造函数又是一个样板,我们可以避免使用紧张的构造函数。在紧凑的构造函数中,我们省略了所有参数,包括括号。组件将自动分配到各自的字段。

我们可以以更紧凑的形式重写上述规范构造函数:

public record Employee(Long id, String firstName, String lastName, String email, int age) {

  public Employee {
    Objects.requireNonNull(id);
    Objects.requireNonNull(email);

    if (age < 18) {
      throw new IllegalArgumentException("You cannot hire a minor person as employee");
    }
  }

  //Other methods
}

‘紧凑型构造函数不会导致编译器生成单独的构造函数。相反,您在紧凑构造函数中指定的代码在规范构造函数的开头显示为额外代码。 We do not need to specify the assignment of constructor parameters to fields as it happens in the canonical constructor in the usual manner.

与类一样,我们可以声明额外的自定义构造函数,但任何自定义构造函数都必须以显式调用规范构造函数作为其第一个语句。它有助于设置新自定义构造函数中不存在的所有组件的默认值。

public record Employee(Long id, String firstName, String lastName, String email, int age) {

  public Employee(Long id, String firstName, String lastName){
    this(id, firstName, lastName, "nobody@domain.com", 18);
    
    //More code
  }

  //Other methods
}

4. 添加字段和方法

Adding a new field and method is 可能,但不推荐. A new field added to 记录(not added to the component list) must be 静态的. Adding a method is also possible that can access the internal state of record fields.

添加的字段和方法不会在编译器隐式生成的字节码中使用,因此它们不是任何方法实现的一部分,例如等于()哈希码()toString()。我们必须根据需要明确地使用它们。

public record Employee(Long id, String firstName, String lastName, String email, int age) {

  static boolean minor;

  public boolean isMinor() {
    return minor;
  }

  public String fullName() {
    return firstName + " " + lastName;
  }

  public Employee {
    if (age < 18) {
      minor = true;
    }
  }
}

现在我们可以访问类似于其他Java类的信息:

Employee employee = new Employee(1l, "Amit", "Gupta", "email@domain.com", 17);

System.out.println(employee.isMinor());   //true
System.out.println(employee.fullName());  //Amit Gupta

5. 通用记录

Record types support 仿制药, similar to other types in Java. A typical example of generic 记录 is as follows;

record Container<T>(int id, T value) {
}

我们可以像下面这样使用这个记录来支持多种类型:

Container<Integer> intContainer = new Container<>(1, Integer.valueOf(1));
Container<String> stringContainer = new Container<>(1, "1");

Integer intValue = intContainer.value();
String strValue = stringContainer.value();

6. 序列化与反序列化

Java 记录顺序列表化与其不同用于常规课程。记录对象的序列化形式是从对象的最终实例字段派生的值序列。记录对象的流格式与流中的普通对象相同。

反序列表化期间,如果指定流类描述符的本地类等价物是记录类,则首先读取并重构流字段以作为记录的组件值。其次,通过使用组件值作为参数调用记录的规范构造函数来创建记录对象(如果流中没有组件值,则使用组件类型的默认值)。

记录类的serialVersionUID0升,除非明确声明。记录类也免除了匹配 serialVersionUID 值的要求。

The process by which record objects are serialized cannot be customized; any class-specific 写入对象, 读取对象, 读取对象无数据, 读取解析, 写外部, and 读取外部 methods defined by record classes are ignored during serialization and deserialization. However, the 写替换 method may be used to return a substitute object to be serialized.

Before performing any 连载 or deserialization, we must ensure that 记录必须是可序列化的或可外部化的.

public record Employee (...) implements Serializable { }
Employee e = new Employee(1l, "Lokesh", "Gupta", "cundage@gmail.com", 38);

writeToFile(e, "employee1");
Employee temp =  readFromFile("employee1");

static void writeToFile(Employee obj, String path) {
  try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path))) {
    oos.writeObject(obj);
  } catch (IOException e) {
    e.printStackTrace();
  }
}

static Employee readFromFile(String path) {
  Employee result = null;
  try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path))) {
    result = (Employee) ois.readObject();
  } catch (ClassNotFoundException | IOException e) {
    e.printStackTrace();
  }
  return result;
}

7. 技术深入探讨

7.1.动态调用

如果我们看到Java编译器生成的字节码来检查toString()的方法实现(以及等于()哈希码()< /code>),它们是使用基于动态调整的机制实现的。

动态调用 is a bytecode instruction that facilitates the implementation of dynamic languages (for the JVM) through 动态方法调用.

7.2.我们不能显式地扩展 Record 类

虽然所有的记录都扩展java.lang.记录类,但我们仍然不能显式地创建java.lang.记录的子类。编译器不会通过。

final class Data extends Record {   // Compiler error : The type Data may not subclass Record explicitly
	private final int unit;
}

这意味着获得记录 的唯一方法是显式声明一个并让javac 创建类文件。

7.3.使用注解

我们可以为记录的组成部分添加注释。例如,我们可以将@短暂停的注解应用到ID字段。

public record Employee(
		@Transient Long id,
		String firstName,
		String lastName,
		String email,
		int age) {
	// ...
}

但请记住,记录具有自动生成的字段和组件访问器。为支持注释这些功能,任何带有目标场地范围方法 的注释在应用于组件时都会传播到相应的位置.

除了现有目标外,新目标 元素类型.RECORD_COMPONENT 是为了在 Records 中进行更细粒度的注释控制而引入的。

8.JDK 的变化

8.1. java.lang.类

班级 类添加了两个方法 – isRecord()getRecordComponents ()getRecordComponents() 方法返回一组记录组件 对象。组件返回的顺序与它们在记录头中声明的顺序相同。

记录组< java.lang.反射 包中的一个新类,它有11 个方法用于检索注释的细节和泛型类型等内容。

8.2. 元素类型 enumeration

The 元素类型 enumeration has a new constant for records, 记录组件.

8.3. javax.lang.model.element 元素

The 元素种类 enumeration has three new constants for the records and pattern matching for 特征实例, namely 绑定变量, 记录 and 记录组件.

9. 什么时候使用?

  • Records are ideal candidates when modeling things like 领域模型类 (potentially to be persisted via ORM), or 数据传输对象 (DTO).
  • 这些记录在临时存储数据时很有用。一个例子可以是JSON 反序列表化期间。通常,我们不希望程序在反序列化期间改变从 JSON 读取的数据。我们只是读取数据并将其传递给数据处理器或验证器。
  • Also, records are 不是可变 Java bean 的替代品 because a record, by design, is immutable.
  • Use records when a class is intended to hold the data for some time, and we want to 避免编写大量样板代码.
  • 我们可以在各种其他情况下使用记录,例如保存来自方法、流连接、复合键和数据结构(如树节点)的多个返回值。

10.结论

Java 记录是一个非常有用的特性,是对 Java 类型系统的一个很好的补充。它有助于几乎完全减少为简单数据载体类编写的样板代码。

但我们应该小心使用它,不要试图自定义它的行为。它最好以默认形状使用。如果您开始寻找在应用程序中自定义 记录 类型的方法,请首先考虑将其转换为简单的 班级 类型。

快乐学习!!

地址:https://www.cundage.com/article/java-14-record-type.html

相关阅读

了解 Java 中的记录类型。它在 Java 14 中作为预览功能引入,并在稍后完成。 Java 记录应该用作不可变的 POJO,以在类和应用程序之间传输数据。 1. 什么是记录? 和张举一样,...
Java 14 于 2020 年 3 月 17 日全面上市。在本文中,我们将介绍 Java 中添加的 16 个新特性 编程语言列表中的一些功能。 We can find the JDK 14 二...
Learn about various Java 中的数据类型. Learn the differences between 原始数据类型 and non-primitive datatypes...
构建器模式旨在通过将构建过程与数据结构的最终表示分离,为构建复杂数据结构提供灵活的解决方案。我们使用构建器模式来获得一个可变的中间变量和一个不可变的最终结果。 Java ‘record‘ 类型是...
Java int to String 教程展示了如何将整数转换为字符串。有几种方法可以在 Java 中执行 int 到 String 的转换。我们可以使用字符串连接、字符串格式化、字符串构建,并...
Java 中的所有值都分为两类:引用类型 和原始类型。了解所有 Java 中的八种基本数据类型、它们的内存大小、默认值以及最大值和最小值范围。 1. Java 原始类型 原始 数据类型 由 Ja...
了解 Java 包装类,它们的用法,原语和对象之间的转换;以及带有示例的自动装箱和拆箱。 1. Java 包装类 在 Java 中,我们有 8 种原始数据类型。 Java 提供类型包装器,它们是...
Java 记录教程展示了如何在 Java 中使用记录类型。 Record 是一种主要用于保存不可变数据的类型。 记录是一种旨在保存不可变数据的类型。这对数据分析非常有用。记录类型简化了代码并提高...
Spring Boot RowMapper 教程展示了如何将 ResultSet 的行映射到数据载体。我们使用 Java 记录作为数据载体。对于本教程,我们需要 JDK 14 并启用预览功能。 ...
Spring Boot Data JPA 派生查询教程展示了如何从方法名称创建查询。 春天 是用于创建企业应用程序的流行 Java 应用程序框架。 弹弓贴 是 Spring 框架的演变,它有助于...