实战Java16值类型Record-2.Record的基本⽤法
在上⼀篇⽂章中,我们详细分析了 Record ⾃带的属性以及⽅法和底层字节码与实现。这⼀篇我们来详细说明 Record 类的⽤法。
声明⼀个 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) {}
}
也可以作为⼀个本地类,即:
public class RecordTest {
public void test() {
record Mail (long id, String content){}
Mail mail = new Mail(10, "content");
}
}
不能⽤ abstract 修饰 Record 类,会有编译错误。
可以⽤ final 修饰 Record 类,但是这其实是没有必要的,因为 Record 类本⾝就是 final 的。
成员 Record 类,还有本地 Record 类,本⾝就是 static 的,也可以⽤ static 修饰,但是没有必要。
和普通类⼀样,Record 类可以被 public, protected, private 修饰,也可以不带这些修饰,这样就是 package-private 的。
和⼀般类不同的是,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 {}
java的tostring方法
public record User(@A @B long id, String name, int age) {}
但是,需要注意⼀点,这⾥通过反射获取 id 的注解的时候,需要通过对应的⽅式进⾏获取,否则获取不到,即ElementType.FIELD通过Field 获取,ElementType.RECORD_COMPONENT通过RecordComponent获取:
Field[] fields = DeclaredFields();
Annotation[] annotations = fields[0].getAnnotations(); // 获取到注解 @B
RecordComponent[] recordComponents = RecordComponents();
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 = DeclaredMethod("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()在编译的时候⾃动⽣成字节码实现,核⼼逻辑基于ObjectMethods的makeHashCode⽅法,⾥⾯的逻辑是对于每⼀个属性的哈希
值移位组合起来。注意这⾥的所有调⽤(包括对于ObjectMethods的⽅法调⽤以及获取每个属性)都是利⽤MethodHandle实现的近似于直接调⽤的⽅式调⽤的。
2. equals()在编译的时候⾃动⽣成字节码实现,核⼼逻辑基于ObjectMethods的makeEquals⽅法,⾥⾯的逻辑是对于两个 Record 对象每⼀
个属性判断是否相等(对于引⽤类型⽤Objects.equals(),原始类型使⽤ ==),注意这⾥的所有调⽤(包括对于ObjectMethods的⽅法调⽤以及获取每个属性)都是利⽤MethodHandle实现的近似于直接调⽤的⽅式调⽤的。
3. toString()在编译的时候⾃动⽣成字节码实现,核⼼逻辑基于ObjectMethods的makeToString⽅法,⾥⾯的逻辑是对于每⼀个属性的值组合
起来构成字符串。注意这⾥的所有调⽤(包括对于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:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论