在 Java 中使用 Record 创建不可变对象

  • 通常创建后内容不改变的对象很有用,可阅读Immutable Objects in Java了解构建此类类的完整描述。
  • 以构建PersonClass为例,要创建不可变实例,此类需:有初始化字段的构造函数、将字段设为privatefinal以确保构造函数设置后不可更改、提供获取字段的方法、不可扩展并标记为final、重写equalshashCodetoString方法。
  • 在 Java 16 之前构建此类需 40 行代码,如下:

    package com.davidemarino;
    
    import java.util.Objects;
    
    public final class PersonClass {
      private final String firstName;
      private final String lastName;
    
      public PersonClass(String firstName, String lastName) {
          this.firstName = firstName;
          this.lastName = lastName;
      }
    
      public String getFirstName() {
          return firstName;
      }
    
      public String getLastName() {
          return lastName;
      }
    
      @Override
      public boolean equals(Object o) {
          if (o == null || getClass()!= o.getClass()) return false;
          PersonClass that = (PersonClass) o;
          return Objects.equals(firstName, that.firstName) && Objects.equals(lastName, that.lastName);
      }
    
      @Override
      public int hashCode() {
          return Objects.hash(firstName, lastName);
      }
    
      @Override
      public String toString() {
          return "PersonClass{" +
                  "firstName='" + firstName + '\'' +
                  ", lastName='" + lastName + '\'' +
                  '}';
      }
    }
  • 可按如下使用:

    package com.davidemarino;
    
    public class Main {
      public static void main(String[] args) {
          PersonClass personClass = new PersonClass("John", "Doe");
          System.out.println("First name: " + personClass.getFirstName());
          System.out.println("Last name: " + personClass.getLastName());
          System.out.println(personClass);
      }
    }
  • Java 16 后可使用record关键字定义此类类,如下:

    package com.davidemarino;
    
    public record PersonRecord(String firstName, String lastName) { }

    使用方式如下:

    package com.davidemarino;
    
    public class Main {
      public static void main(String[] args) {
          PersonClass personClass = new PersonClass("John", "Doe");
          System.out.println("First name: " + personClass.getFirstName());
          System.out.println("Last name: " + personClass.getLastName());
          System.out.println(personClass);
    
          PersonRecord personRecord = new PersonRecord("John", "Doe");
          System.out.println("First name: " + personRecord.firstName());
          System.out.println("Last name: " + personRecord.lastName());
          System.out.println(personRecord);
      }
    }

    区别在于获取方法的命名约定不同,传统方式为getFirstName()getLastName()record方式为firstName()lastName()

  • 若有可变对象字段,如在PersonClass中添加List<String> preferences字段,传统方式需在构造函数和获取方法中创建字段副本,如下:

    package com.davidemarino;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Objects;
    
    public class PersonClass {
      private final String firstName;
      private final String lastName;
      private final List<String> preferences;
      
      public PersonClass(String firstName, String lastName, List<String> preferences) {
          this.firstName = firstName;
          this.lastName = lastName;
          this.preferences = new ArrayList<>(preferences);
      }
    
      public String getFirstName() {
          return firstName;
      }
    
      public String getLastName() {
          return lastName;
      }
    
      public List<String> getPreferences() {
          return new ArrayList<>(preferences);
      }
    
      @Override
      public boolean equals(Object o) {
          if (o == null || getClass()!= o.getClass()) return false;
          PersonClass that = (PersonClass) o;
          return Objects.equals(firstName, that.firstName) && Objects.equals(lastName, that.lastName) && Objects.equals(preferences, that.preferences);
      }
    
      @Override
      public int hashCode() {
          return Objects.hash(firstName, lastName, preferences);
      }
    
      @Override
      public String toString() {
          return "PersonClass{" +
                  "firstName='" + firstName + '\'' +
                  ", lastName='" + lastName + '\'' +
                  ", preferences=" + preferences +
                  '}';
      }
    }

    使用record方式如下:

    package com.davidemarino;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public record PersonRecord(String firstName, String lastName, List<String> preferences) {
      public PersonRecord {
          preferences = new ArrayList<>(preferences);
      }
    
      public List<String> preferences() {
          return new ArrayList<>(preferences);
    }

    此部分代码称为紧凑规范构造函数,相当于以下标准构造函数:

      public PersonRecord(String firstName, String lastName, List<String> preferences) {
          preferences = new ArrayList<>(preferences);
          this.firstName = firstName;
          this.lastName = lastName;
          this.preferences = preferences;
      }
  • 结论:传统创建 Java 不可变对象需大量样板代码,Java 16 引入record后,开发者有简洁且表达力强的方式定义不可变数据结构,自动生成构造函数、封装字段并重写equals等方法,减少手动编码工作量,使代码更易读、维护且不易出错,是 Java 类型系统的强大补充,简化了健壮不可变数据模型的创建。
阅读 27
0 条评论