Java 16 新特性:record类
以前我们定义类都是用class关键词,但从Java 16开始,我们将多一个关键词record,它也可以用来定义类。record关键词的引入,主要是为了提供一种更为简洁、紧凑的final类的定义方式。
有一下几种使用方式
-
单独文件申明
public record Student(String name, int age) { }
-
其他类中声明
public class DidispaceTest { public record range(int start, int end){} }
函数中声明
public class DidispaceTest {
public void test() {
public record range(int start, int end){}
}
}
record分析
record关键词申明类主要是为了简化一些类的申明,所以它本质就是一类特殊的class,我们来看看它到底有什么不一样
public record Student( String name, int age) {
}
编译上面代码
//Student.class
public record Student(String name, int age) {
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String name() {
return this.name;
}
public int age() {
return this.age;
}
}
可以看到多了几个成员变量的对应函数(类似get函数)
反编译上面的代码
Classfile /home/jelly/JavaProject/RecordDemo/out/production/RecordDemo/Student.class
Last modified 2024年8月7日; size 1402 bytes
SHA-256 checksum fe5b03bffc16459688921a4e7a9c119861bfeb8e9aacd5ca1c04935079dca006
Compiled from "Student.java"
public final class Student extends java.lang.Record
// RECORD
// access flags 0x10031
{
// compiled from: Student.java
// access flags 0x19
public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup
RECORDCOMPONENT Ljava/lang/String; name
RECORDCOMPONENT I age
// access flags 0x12
private final Ljava/lang/String; name
// access flags 0x12
private final I age
// access flags 0x1
public <init>(Ljava/lang/String;I)V
// parameter name
// parameter age
L0
LINENUMBER 1 L0
ALOAD 0
INVOKESPECIAL java/lang/Record.<init> ()V
ALOAD 0
ALOAD 1
PUTFIELD Student.name : Ljava/lang/String;
ALOAD 0
ILOAD 2
PUTFIELD Student.age : I
RETURN
L1
LOCALVARIABLE this LStudent; L0 L1 0
LOCALVARIABLE name Ljava/lang/String; L0 L1 1
LOCALVARIABLE age I L0 L1 2
MAXSTACK = 2
MAXLOCALS = 3
// access flags 0x11
public final toString()Ljava/lang/String;
L0
LINENUMBER 1 L0
ALOAD 0
INVOKEDYNAMIC toString(LStudent;)Ljava/lang/String; [
// handle kind 0x6 : INVOKESTATIC
java/lang/runtime/ObjectMethods.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
// arguments:
Student.class,
"name;age",
// handle kind 0x1 : GETFIELD
Student.name(Ljava/lang/String;),
// handle kind 0x1 : GETFIELD
Student.age(I)
]
ARETURN
L1
LOCALVARIABLE this LStudent; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x11
public final hashCode()I
L0
LINENUMBER 1 L0
ALOAD 0
INVOKEDYNAMIC hashCode(LStudent;)I [
// handle kind 0x6 : INVOKESTATIC
java/lang/runtime/ObjectMethods.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
// arguments:
Student.class,
"name;age",
// handle kind 0x1 : GETFIELD
Student.name(Ljava/lang/String;),
// handle kind 0x1 : GETFIELD
Student.age(I)
]
IRETURN
L1
LOCALVARIABLE this LStudent; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x11
public final equals(Ljava/lang/Object;)Z
L0
LINENUMBER 1 L0
ALOAD 0
ALOAD 1
INVOKEDYNAMIC equals(LStudent;Ljava/lang/Object;)Z [
// handle kind 0x6 : INVOKESTATIC
java/lang/runtime/ObjectMethods.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
// arguments:
Student.class,
"name;age",
// handle kind 0x1 : GETFIELD
Student.name(Ljava/lang/String;),
// handle kind 0x1 : GETFIELD
Student.age(I)
]
IRETURN
L1
LOCALVARIABLE this LStudent; L0 L1 0
LOCALVARIABLE o Ljava/lang/Object; L0 L1 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1
public name()Ljava/lang/String;
L0
LINENUMBER 1 L0
ALOAD 0
GETFIELD Student.name : Ljava/lang/String;
ARETURN
L1
LOCALVARIABLE this LStudent; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public age()I
L0
LINENUMBER 1 L0
ALOAD 0
GETFIELD Student.age : I
IRETURN
L1
LOCALVARIABLE this LStudent; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
}
看看他的toString
public final toString()Ljava/lang/String;
L0
LINENUMBER 1 L0
ALOAD 0//将当前对象(即 Student 实例)引用压入操作数栈。在 Java 中,方法的第一个参数(this)总是位于局部变量表的第0位。
INVOKEDYNAMIC toString(LStudent;)Ljava/lang/String; [//INVOKEDYNAMIC动态链接到一个函数:toString 函数
// handle kind 0x6 : INVOKESTATIC
//通过以下引导方法指定的:指定动态链接的toString函数
//我们看一下这个引导函数bootstrap定义
// public static Object bootstrap(MethodHandles.Lookup lookup, String methodName, TypeDescriptor type,
// Class<?> recordClass,
// String names,
// MethodHandle... getters)
//lookup:一个查找处理
//methodName:要生成的方法的名称,必须是“equals”、“hashCode”或“toString”中的一个。
//type:对应于方法的描述符类型
//recordClass:承载记录组件的记录类
//names:组件名称列表,以“;”分隔,如果没有组件则为空字符串。如果methodName参数为"equals"或"hashCode",则忽略此参数。
//getters:这是一个 MethodHandle 数组,用于指定动态调用中需要使用的字段或方法的句柄
//可以看出recordClass=Student.class;names="name;age";getters=Student.name(Ljava/lang/String;), Student.age(I)
//所以他的toString 输出的是 Student[name=张三, age=17];
java/lang/runtime/ObjectMethods.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
// arguments:
Student.class,
"name;age",
// handle kind 0x1 : GETFIELD
Student.name(Ljava/lang/String;),
// handle kind 0x1 : GETFIELD
Student.age(I)
]
ARETURN//从当前方法返回,返回值是操作数栈顶部的值。
L1
LOCALVARIABLE this LStudent; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
//所以调用toString实际上调用的是下面代码
Object a = ObjectMethods.bootstrap(lookup, "toString", MethodHandle.class, Student.class, "name", getterMh);
看看他的其他函数hashCode,equals
public final hashCode()I
L0
LINENUMBER 1 L0
ALOAD 0
INVOKEDYNAMIC hashCode(LStudent;)I [
// handle kind 0x6 : INVOKESTATIC
java/lang/runtime/ObjectMethods.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
// arguments:
Student.class,
"name;age",
// handle kind 0x1 : GETFIELD
Student.name(Ljava/lang/String;),
// handle kind 0x1 : GETFIELD
Student.age(I)
]
IRETURN
L1
LOCALVARIABLE this LStudent; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x11
public final equals(Ljava/lang/Object;)Z
L0
LINENUMBER 1 L0
ALOAD 0
ALOAD 1
INVOKEDYNAMIC equals(LStudent;Ljava/lang/Object;)Z [
// handle kind 0x6 : INVOKESTATIC
java/lang/runtime/ObjectMethods.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
// arguments:
Student.class,
"name;age",
// handle kind 0x1 : GETFIELD
Student.name(Ljava/lang/String;),
// handle kind 0x1 : GETFIELD
Student.age(I)
]
IRETURN
L1
LOCALVARIABLE this LStudent; L0 L1 0
LOCALVARIABLE o Ljava/lang/Object; L0 L1 1
MAXSTACK = 2
MAXLOCALS = 2
通过观察发现实际上这个toString代码大同小异,都是被INVOKEDYNAMIC链接到动态链接到对应的函数,最终实际调用还是bootstrap
bootstrap
我们先看一下这个函数定义
Bootstrap method to generate the Object.equals(Object), Object.hashCode(), and Object.toString() methods, based on a description of the component names and accessor methods, for either invokedynamic call sites or dynamic constant pool entries. For more detail on the semantics of the generated methods see the specification of Record.equals(Object), Record.hashCode() and Record.toString().
Params:
lookup – Every bootstrap method is expected to have a lookup which usually represents a lookup context with the accessibility privileges of the caller. This is because invokedynamic call sites always provide a lookup to the corresponding bootstrap method, but this method just ignores the lookup parameter methodName – the name of the method to generate, which must be one of "equals", "hashCode", or "toString" type – a MethodType corresponding the descriptor type for the method, which must correspond to the descriptor for the corresponding Object method, if linking an invokedynamic call site, or the constant MethodHandle.class, if linking a dynamic constant recordClass – the record class hosting the record components names – the list of component names, joined into a string separated by ";", or the empty string if there are no components. This parameter is ignored if the methodName parameter is "equals" or "hashCode" getters – method handles for the accessor methods for the components
Returns:
a call site if invoked by indy, or a method handle if invoked by a condy
Throws:
IllegalArgumentException – if the bootstrap arguments are invalid or inconsistent
NullPointerException – if any argument is null or if any element in the getters array is null
Throwable – if any exception is thrown during call site construction
public static Object bootstrap(MethodHandles.Lookup lookup, String methodName, TypeDescriptor type,
Class<?> recordClass,
String names,
MethodHandle... getters) throws Throwable
//用于根据组件名称和访问器方法的描述为 invokedynamic 调用站点或动态常量池条目生成 Object.equals(Object) 、 Object.hashCode() 和 Object.toString() 方法的 Bootstrap 方法。有关生成方法的语义的更多详细信息
lookup - 每个引导方法都应该有一个 lookup ,它通常表示具有调用者可访问权限的查找上下文。这是因为 invokedynamic 调用站点总是提供一个 lookup 给相应的引导方法,但是这个方法只是忽略了 lookup 参数,
methodName - 要生成的方法的名称,必须是 "equals" 、 "hashCode" 或 "toString" 之一
type - 对应于方法的描述符类型的 MethodType ,如果链接 invokedynamic 调用站点,则必须对应于相应 Object 方法的描述符;如果链接动态常量,则必须对应于常量 MethodHandle.class
recordClass - 托管记录组件的记录类
names - 组件名称列表,连接成一个由 ";" 分隔的字符串,如果没有组件则为空字符串。如果 methodName 参数为 "equals" 或 "hashCode" ,则忽略此参数
getters - 组件访问器方法的方法句柄
手动调用
public class Student {
public Student(String name, int age) {
this.name = name;
this.age = age;
}
private String name;
private int age;
public String name() {
return name;
}
public int age() {
return age;
}
}
public static void main(String[] args) throws Throwable {
Student student = new Student("张三", 17);
System.out.println(student.toString());
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType nameMt = MethodType.methodType(String.class);
MethodHandle nameMh = lookup.findVirtual(Student.class, "name", nameMt);
MethodType ageMt = MethodType.methodType(int.class);
MethodHandle ageMh = lookup.findVirtual(Student.class, "age", ageMt);
Object a = ObjectMethods.bootstrap(lookup, "toString", MethodHandle.class, Student.class, "name;age", nameMh,ageMh);
System.out.println(((MethodHandle) a).invoke(student).toString());
}
//输出
Student@372f7a8d
Student[name=张三, age=17]
评论区