record

Java 14 中预览的新特性叫做 Record 。
在 Java 中,Record 是一种特殊类型的 Java 类,可用来创建不可变类。
参考 JEP 395
Jackson 2.12 支持 Record 。

任何时候创建 Java 类,都会创建大量的样板代码:

  1. 每个字段的 set ,get 方法
  2. 公共的构造方法
  3. 重写 hashCode ,toString,equals 方法
    Record 避免上述的样板代码,有如下特点:
  4. 带有全部参数的构造方法
  5. public 访问器
  6. toString() ,hashCode() ,equals()
  7. 无 set ,get 方法,没有遵循 Bean 的命名规范
  8. final 类,不能继承 Record 。Record 为隐式的 final 类,除此之外与普通类一样
  9. 不可变类,通过构造创建 Record
  10. final 属性,不可修改
  11. 不能声明实例属性,能声明 static 成员

用法

  1. 创建 Student Record
public record Student(Integer id, String name, String email, Integer age) {}
  1. 创建 Record 对象
public static void main(String[] args) {
    Student lisi = new Student(1001, "lisi", "lisi@qq.com", 20);
    System.out.println("lisi = " + lisi.toString());
    Student zhangsan = new Student(1002, "zhangsan", "lisi@qq.com", 20);
    System.out.println("zhangsan = " + zhangsan.toString());
    System.out.println("lisi.equals(zhangsan) = " + lisi.equals(zhangsan));
    System.out.println("lisi.name() = " + lisi.name());
    System.out.println("zhangsan.name() = " + zhangsan.name());
}

控制台输出:

lisi = Student[id=1001, name=lisi, email=lisi@qq.com, age=20]
zhangsan = Student[id=1002, name=zhangsan, email=lisi@qq.com, age=20]
lisi.equals(zhangsan) = false
lisi.name() = lisi
zhangsan.name() = zhangsan

Record 通过构造方法创建了只读的对象,能够读取每个属性,不能设置新的属性值。
Record 对每个属性提供了 public 访问器,例如 lisi.name() 。

实例方法

Record 是 Java 类,和普通 Java 类一样定义方法。我们创建普通的方法 concat ,将 name 和 age 连接为一个字符串输出。

  1. 创建实例方法
public record Student(Integer id, String name, String email, Integer age) {
    public String concat() {
        return String.format("姓名:%s,年龄是:%d", this.name, this.age);
    }
}
  1. 调用实例方法
public static void main(String[] args) {
    Student lisi = new Student(1001, "lisi", "lisi@qq.com", 20);
    String nameAndAge = lisi.concat();
    System.out.println(nameAndAge);
}

控制台输出:

姓名:lisi,年龄是:20

静态方法

Record 类定义静态方法,使用静态方法与普通类一样。

  1. 创建静态方法
public record Student(Integer id, String name, String email, Integer age) {
    public String concat() {
        return String.format("姓名:%s,年龄是:%d", this.name, this.age);
    }
    /** 静态方法 */
    public static String emailUpperCase(String email) {
        return Optional.ofNullable(email).orElse("no email").toUpperCase();
    }
}
  1. 测试静态方法
public static void main(String[] args) {
    String emailUpperCase = Student.emailUpperCase("lisi@163.com");
    System.out.println("emailUpperCase = " + emailUpperCase);
}

构造方法

我们可以在 Record 中添加构造方法,有三种类型的构造方法:

  1. 紧凑型构造方法没有任何参数,甚至没有括号
  2. 规范构造方法是以所有成员作为参数
  3. 定制构造方法是自定义参数个数
  1. 紧凑和定制构造方法
public record Student(Integer id, String name, String email, Integer age) {
    /*紧凑构造方法*/
    public Student {
        System.out.println("id"+ id );
        if( id < 1 ){
            throw new RuntimeException("ok");
        }
    }
    /*自定义构造方法*/
    public Student(Integer id, String name) {
        this(id, name, null, null);
    }
}
  1. 编译结果
public record Student(Integer id, String name, String email, Integer age) {
    /** 紧凑构造方法和规范构造方法合并了 */
    public Student(Integer id, String name, String email, Integer age) {
        System.out.println("id" + id);
        if (id < 1) {
            throw new RuntimeException("ok"); }
        else {
            this.id = id;
            this.name = name;
            this.email = email;
            this.age = age;
        }
    }
    public Student(Integer id, String name) {
        this(id, name, (String)null, (Integer)null);
    }
}

接口

Java Record 可以与普通类一样实现接口,重写接口的方法。

  1. 创建新的接口,定义一个规范方法。
public interface PrintInterface {
    /** 输出自定义描述信息 */
    void print();
}
  1. 创建新的 Record 实现接口,重写接口的方法,实现当前 Record 有关的业务逻辑
public record ProductRecord(String id, String name, Integer qty) implements PrintInterface {
    @Override
    public void print() {
        String productDesc = String.join("-", id, name, qty.toString());
        System.out.println("商品信息 = " + productDesc);
    }
}
  1. 测试 print 方法
public static void main(String[] args) {
    ProductRecord product = new ProductRecord("P001", "手机", 100);
    product.print();
}

局部 Record

Record 可以作为局部对象使用。在代码块中定义并使用 Record,下面定义一个 SaleRecord :

  1. 定义 Local Record
public static void main(String[] args) {
    // 定义 Java Record
    record SaleRecord(String saleId, String productName, Double money){}; // 创建 Local Record
    SaleRecord saleRecord = new SaleRecord("S22020301", "手机", 3000.0); // 使用 SaleRecord
    System.out.println("销售记录 = " + saleRecord.toString());
}
  1. 控制台输出
销售记录 = SaleRecord[saleId=S22020301, productName=手机, money=3000.0]

嵌套 Record

多个 Record 可以组合定义, 一个 Record 能够包含其他的 Record 。
我们定义 Record 为 Customer,存储客户信息,包含了 Address 和 PhoneNumber 两个 Record:

  1. 定义 Record
public record Address(String city, String address, String zipcode) {}
public record PhoneNumber(String areaCode, String number) {}
public record Customer(String id, String name, PhoneNumber phoneNumber, Address address) {}
  1. 创建 Customer 对象
public static void main(String[] args) {
    Address address = new Address("北京", "大兴区凉水河二街-8号10栋三层", "100176");
    PhoneNumber phoneNumber = new PhoneNumber("010", "400-8080-105");
    Customer customer = new Customer("C1001", "李项", phoneNumber, address);
    System.out.println("客户 = " + customer.toString());
}
  1. 控制台输出
客户 = Customer[id=C1001, name=李项, phoneNumber=PhoneNumber[areaCode=010, number=400-8080-105], address=Address[city=北京, address=大兴区凉水河二街 9 号 10 栋三层, zipcode=100176]]

instanceof 判断 Record 类型

instanceof 能够与 Java Record 一起使用。编译器知道记录组件的确切数量和类型。

  1. 声明 Person Record ,拥有两个属性 name 和 age
public record Person(String name, Integer age) {}
2. 在一个业务方法判断当是 Record 类型时,继续判断 age 年龄是否满足 18 岁
```java
public class SomeService {
    public boolean isEligible(Object obj){
        // 判断 obj 为 Person 记录类型
        if( obj instanceof Person(String name, Integer age)){
            return age >= 18;
        }
        return false;
    }
}

instanceof 还可以下面的方式:

if( obj instanceof Person(String name, Integer age) person) {
    return person.age() >= 18;
}

或者

if(obj instanceof Person p) {
    return p.age() >= 18;
}
  1. 测试代码
public static void main(String[] args) {
    SomeService service = new SomeService();
    boolean flag = service.isEligible(new Person("李四", 20));
    System.out.println("年龄符合吗?" + flag);
}

控制台输出:

控制台输出 flag 为 true

处理判断中的 Record 为 null

Java Record 能够自动处理 null 。

  1. record 为 null
public static void main(String[] args) {
    SomeService service = new SomeService();
    boolean eligible = service.isEligible(null);
    System.out.println("年龄符合吗?" + eligible);
}

控制台输出 eligible 为 false ,Debug 调试代码,发现 if 语句判断为 false,不执行。

总结

  1. abstract 类 java.lang.Record 是所有 Record 的父类。
  2. 有对于 equals() ,hashCode() ,toString() 的定义说明
  3. Record 类能够实现 java.io.Serializable 序列化或反序列化
  4. Record 支持泛型,例如 record Gif(T t) {}
  5. java.lang.Class 类与 Record 类有关的两个方法:

boolean isRecord() : 判断一个类是否是 Record 类型
RecordComponent[] getRecordComponents():Record 的数组,表示此记录类的所有记录组件

Customer customer = new Customer(....);
RecordComponent[] recordComponents = customer.getClass().getRecordComponents();
for (RecordComponent recordComponent : recordComponents) {
    System.out.println("recordComponent = " + recordComponent);
}
boolean record = customer.getClass().isRecord();
System.out.println("record = " + record);
posted @ 2023-04-28 19:54  HopeLive  阅读(216)  评论(0)    收藏  举报