实战 Java 16 值类型 Record - 2. Record 的基本用法

在上一篇文章实战 Java 16 值类型 Record - 1. Record 的默认方法使用以及基于预编译生成相关字节码的底层实现中,咱们详细分析了 Record 自带的属性以及方法和底层字节码与实现。这一篇咱们来详细说明 Record 类的用法。java

声明一个 Record

Record 能够单独做为一个文件的顶级类,即:
User.java 文件:编程

public record User(long id, String name, int age) {}

也能够做为一个成员类,即:微信

public class RecordTest {
    public record User(long id, String name, int age) {}
}

也能够做为一个本地类,即:ide

public class RecordTest {
    public void test() {
        record Mail (long id, String content){}
        Mail mail = new Mail(10, "content");
    }
}

不能用 abstract 修饰 Record 类,会有编译错误。
能够用 final 修饰 Record 类,可是这实际上是没有必要的,由于 Record 类自己就是 final 的post

成员 Record 类,还有本地 Record 类,自己就是 static 的,也能够用 static 修饰,可是没有必要。this

和普通类同样,Record 类能够被 public, protected, private 修饰,也能够不带这些修饰,这样就是 package-private 的。code

和通常类不一样的是,Record 类的直接父类不是 java.lang.Object 而是 java.lang.Record。可是,Record 类不能使用 extends,由于 Record 类不能继承任何类。对象

Record 类的属性

通常,在 Record 类声明头部指定这个 Record 类有哪些属性继承

public record User(long id, String name, int age) {}

同时,能够在头部的属性列表中运用注解:字符串

@Target({ ElementType.RECORD_COMPONENT})
@Retention(RetentionPolicy.RUNTIME)
public @interface A {}
@Target({ ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface B {}

public record User(@A @B long id, String name, int age) {}

可是,须要注意一点,这里经过反射获取 id 的注解的时候,须要经过对应的方式进行获取,不然获取不到,即 ElementType.FIELD 经过 Field 获取,ElementType.RECORD_COMPONENT 经过 RecordComponent 获取:

Field[] fields = User.class.getDeclaredFields();
Annotation[] annotations = fields[0].getAnnotations(); // 获取到注解 @B

RecordComponent[] recordComponents = User.class.getRecordComponents();
annotations = recordComponents[0].getAnnotations(); // 获取到注解 @A

Record 类体

Record 类属性必须在头部声明,在 Record 类体只能声明静态属性

public record User(long id, String name, int age) {
    static long anotherId;
}

Record 类体能够声明成员方法和静态方法,和通常类同样。可是不能声明 abstract 或者 native 方法

public record User(long id, String name, int age) {
    public void test(){}
    public static void test2(){}
}

Record 类体也不能包含实例初始化块,例如:

public record User(@A @B long id, String name, int age) {
    {
        System.out.println(); //编译异常
    }
}

Record 成员

Record 的全部成员属性,都是 public final 非 static 的,对于每个属性,都有一个对应的无参数返回类型为属性类型方法名称为属性名称的方法,即这个属性的 accessor。前面说这个方法是 getter 方法其实不太准确,由于方法名称中并无 get 或者 is 而是只是纯属性名称做为方法名。

这个方法若是咱们本身指定了,就不会自动生成:

public record User(long id) {
    @Override
    public long id() {
        return id;
    }
}

若是没有本身指定,则会自动生成这样一个方法:

  1. 方法名就是属性名称
  2. 返回类型就是对应的属性类型
  3. 是一个 public 方法,而且没有声明抛出任何异常
  4. 方法体就是返回对应属性
  5. 若是属性上面有任何注解,那么这个注解若是能加到方法上那么也会自动加到这个方法上。例如:
    public record User(@A @B long id, String name, int age) {}
    @Target({
        ElementType.RECORD_COMPONENT,
        ElementType.METHOD,
    })
    @Retention(RetentionPolicy.RUNTIME)
    public @interface A {}
    @Target({ ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface B {}

    下面获取 id() 这个方法的注解则会获取到注解 @A

    Method id = User.class.getDeclaredMethod("id");
    Annotation[] idAnnotations = id.getAnnotations(); //@A

因为会自动生成这些方法,因此 Record 成员的名称不能和 Object 的某些不符合上述条件(即上面提到的 6 条)的方法的名称同样,例如:

public record User(
    int wait, //编译错误
    int hashcode, //这个不会有错误,由于 hashcode() 方法符合自动生成的 accessor 的限制条件。
    int toString, //编译错误
    int finalize //编译错误) {
}

Record 类若是没有指定,则默认会生成实现 java.lang.Record 的抽象方法,即hashcode(), equals(), toStrng() 这三个方法。这三个方法的实现方式,在第一节已经详细分析过,这里简单回顾下要点:

  1. hashcode() 在编译的时候自动生成字节码实现,核心逻辑基于 ObjectMethodsmakeHashCode 方法,里面的逻辑是对于每个属性的哈希值移位组合起来。注意这里的全部调用(包括对于 ObjectMethods 的方法调用以及获取每一个属性)都是利用 MethodHandle 实现的近似于直接调用的方式调用的。
  2. equals() 在编译的时候自动生成字节码实现,核心逻辑基于 ObjectMethodsmakeEquals 方法,里面的逻辑是对于两个 Record 对象每个属性判断是否相等(对于引用类型用Objects.equals(),原始类型使用 ==),注意这里的全部调用(包括对于 ObjectMethods 的方法调用以及获取每一个属性)都是利用 MethodHandle 实现的近似于直接调用的方式调用的。
  3. toString() 在编译的时候自动生成字节码实现,核心逻辑基于 ObjectMethodsmakeToString 方法,里面的逻辑是对于每个属性的值组合起来构成字符串。注意这里的全部调用(包括对于 ObjectMethods 的方法调用以及获取每一个属性)都是利用 MethodHandle 实现的近似于直接调用的方式调用的。

Record 构造器

若是没有指定构造器,Record 类会自动生成一个以全部属性为参数而且给每一个属性赋值的构造器。咱们能够经过两种方式本身声明构造器:

第一种是明确声明以全部属性为参数而且给每一个属性赋值的构造器,这个构造器须要知足:

  1. 构造器参数须要包括全部属性(同名同类型),并按照 Record 类头的声明顺序。
  2. 不能声明抛出任何异常(不能使用 throws)
  3. 不能调用其余构造器(即不能使用 this(xxx))
  4. 构造器须要对于每一个属性进行赋值。

对于其余构造器,须要明确调用这个包含全部属性的构造器

public record User(long id, String name, int age) {
    public User(int age, long id) {
        this(id, "name", age);
    }
    public User(long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
}

第二种是简略方式声明,例如:

public record User(long id, String name, int age) {
    public User {
        System.out.println("initialized");
        id = 1000 + id;
        name = "prefix_" + name;
        age = 1 + age;
        //在这以后,对每一个属性赋值
    }
}

这种方式至关于省略了参数以及对于每一个属性赋值,至关于对这种构造器的开头插入代码。

微信搜索“个人编程喵”关注公众号,每日一刷,轻松提高技术,斩获各类offer

image

相关文章
相关标签/搜索