# 疾速上手 Record 类
咱们先举一个简略例子,申明一个用户 Record。
public record User(long id, String name, int age) {}
这样编写代码之后,Record 类默认蕴含的元素和办法实现包含:
- record 头指定的组成元素(
int id, String name, int age
),并且,这些元素都是 final 的。 - record 默认只有一个结构器,是蕴含所有元素的结构器。
- record 的每个元素都有一个对应的 getter(但这种 getter 并不是 getxxx(),而是间接用变量名命名,所以应用序列化框架,DAO 框架都要留神这一点)
- 实现好的 hashCode(),equals(),toString() 办法(通过主动在编译阶段生成对于 hashCode(),equals(),toString() 办法实现的字节码实现)。
咱们来应用下这个 Record :
User zhx = new User(1, "zhx", 29);User ttj = new User(2, "ttj", 25);System.out.println(zhx.id());//1System.out.println(zhx.name());//zhxSystem.out.println(zhx.age());//29System.out.println(zhx.equals(ttj));//falseSystem.out.println(zhx.toString());//User[id=1, name=zhx, age=29]System.out.println(zhx.hashCode());//3739156
Record 的构造是如何实现的
编译后插入相干域与办法的字节码
查看下面举得例子的字节码,有两种形式,一是通过 javap -v User.class
命令查看文字版的字节码,截取重要的字节码如下所示:
//省略文件头,文件常量池局部{ //public 结构器,全副属性作为参数,并给每个 Field 赋值 public com.github.hashzhang.basetest.User(long, java.lang.String, int); descriptor: (JLjava/lang/String;I)V flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=5, args_size=4 0: aload_0 1: invokespecial #1 // Method java/lang/Record."<init>":()V 4: aload_0 5: lload_1 6: putfield #7 // Field id:J 9: aload_0 10: aload_3 11: putfield #13 // Field name:Ljava/lang/String; 14: aload_0 15: iload 4 17: putfield #17 // Field age:I 20: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 21 0 this Lcom/github/hashzhang/basetest/User; 0 21 1 id J 0 21 3 name Ljava/lang/String; 0 21 4 age I MethodParameters: Name Flags id name age //public final 润饰的 toString 办法 public final java.lang.String toString(); descriptor: ()Ljava/lang/String; flags: (0x0011) ACC_PUBLIC, ACC_FINAL Code: stack=1, locals=1, args_size=1 0: aload_0 //外围实现是这个 invokedynamic,咱们前面会剖析 1: invokedynamic #21, 0 // InvokeDynamic #0:toString:(Lcom/github/hashzhang/basetest/User;)Ljava/lang/String; 6: areturn LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 7 0 this Lcom/github/hashzhang/basetest/User; //public final 润饰的 hashCode 办法 public final int hashCode(); descriptor: ()I flags: (0x0011) ACC_PUBLIC, ACC_FINAL Code: stack=1, locals=1, args_size=1 0: aload_0 //外围实现是这个 invokedynamic,咱们前面会剖析 1: invokedynamic #25, 0 // InvokeDynamic #0:hashCode:(Lcom/github/hashzhang/basetest/User;)I 6: ireturn LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 7 0 this Lcom/github/hashzhang/basetest/User; //public final 润饰的 equals 办法 public final boolean equals(java.lang.Object); descriptor: (Ljava/lang/Object;)Z flags: (0x0011) ACC_PUBLIC, ACC_FINAL Code: stack=2, locals=2, args_size=2 0: aload_0 1: aload_1 //外围实现是这个 invokedynamic,咱们前面会剖析 2: invokedynamic #29, 0 // InvokeDynamic #0:equals:(Lcom/github/hashzhang/basetest/User;Ljava/lang/Object;)Z 7: ireturn LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 8 0 this Lcom/github/hashzhang/basetest/User; 0 8 1 o Ljava/lang/Object; //public 润饰的 id 的 getter public long id(); descriptor: ()J flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: getfield #7 // Field id:J 4: lreturn LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/github/hashzhang/basetest/User; //public 润饰的 name 的 getter public java.lang.String name(); descriptor: ()Ljava/lang/String; flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #13 // Field name:Ljava/lang/String; 4: areturn LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/github/hashzhang/basetest/User; //public 润饰的 age 的 getter public int age(); descriptor: ()I flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #17 // Field age:I 4: ireturn LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/github/hashzhang/basetest/User;}SourceFile: "User.java"Record: long id; descriptor: J java.lang.String name; descriptor: Ljava/lang/String; int age; descriptor: I//以下是 invokedynamic 会调用的办法以及参数信息,咱们前面会剖析BootstrapMethods: 0: #50 REF_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; Method arguments: #8 com/github/hashzhang/basetest/User #57 id;name;age #59 REF_getField com/github/hashzhang/basetest/User.id:J #60 REF_getField com/github/hashzhang/basetest/User.name:Ljava/lang/String; #61 REF_getField com/github/hashzhang/basetest/User.age:IInnerClasses: public static final #67= #63 of #65; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
另一种是通过 IDE 的 jclasslib 插件查看,我举荐应用这种办法,查看成果如下:
主动生成的 private final field
主动生成的全属性结构器
主动生成的 public getter 办法
主动生成的 hashCode(),equals(),toString() 办法
这些办法的外围就是 invokedynamic:
看上去貌似是调用另外一个办法,这种间接调用难道没有性能损耗问题么?这一点 JVM 开发者曾经想到了。咱们先来来理解下 invokedynamic。
invokedynamic 产生的背景
Java 最早是一种动态类型语言,也就是说它的类型查看的主体过程次要是在编译期而不是运行期。为了兼容动静类型语法,也是为了 JVM 可能兼容动静语言(JVM 设计初衷并不是只能运行 Java),在 Java 7 引入了字节码指令 invokedynamic。这也是起初 Java 8 的拉姆达表达式以及 var 语法的实现根底。
invokedynamic 与 MethodHandle
invokedynamic 离不开对 java.lang.invoke 包的应用。这个包的次要目标是在之前单纯依附符号援用来确定调用的指标办法这种形式以外,提供一种新的动静确定指标办法的机制,称为MethodHandle
。
通过 MethodHandle
能够动静获取想调用的办法进行调用,和 Java Reflection
反射相似,然而为了谋求性能效率,须要用 MethodHandle
,次要起因是: Reflection
仅仅是 Java 语言上补充针对反射的实现,并没有思考效率的问题,尤其是 JIT 根本无奈针对这种反射调用进行无效的优化。MethodHandle
更是像是对于字节码的办法指令调用的模仿,适当应用的话 JIT 也能对于它进行优化,例如将 MethodHandle
相干办法援用申明为 static final 的:
private static final MutableCallSite callSite = new MutableCallSite( MethodType.methodType(int.class, int.class, int.class));private static final MethodHandle invoker = callSite.dynamicInvoker();
主动生成的 toString(), hashcode(), equals() 的实现
通过字节码能够看出 incokedynamic 理论调用的是 BoostrapMethods
中的 #0 办法:
0 aload_01 invokedynamic #24 <hashCode, BootstrapMethods #0>6 ireturn
Bootstap 办法表包含:
BootstrapMethods: //调用的理论是 java.lang.runtime.ObjectMethods 的 boostrap 办法 0: #50 REF_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; Method arguments: #8 com/github/hashzhang/basetest/User #57 id;name;age #59 REF_getField com/github/hashzhang/basetest/User.id:J #60 REF_getField com/github/hashzhang/basetest/User.name:Ljava/lang/String; #61 REF_getField com/github/hashzhang/basetest/User.age:IInnerClasses: //申明 MethodHandles.Lookup 为 final,放慢调用性能,这样调用 BootstrapMethods 外面的办法能够实现近似于间接调用的性能 public static final #67= #63 of #65; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
从这里,咱们就能看出,实际上 toString() 调用的是 java.lang.runtime.ObjectMethods
的 bootstap()
办法。其外围代码是:ObjectMethods.java
public static Object bootstrap(MethodHandles.Lookup lookup, String methodName, TypeDescriptor type, Class<?> recordClass, String names, MethodHandle... getters) throws Throwable { MethodType methodType; if (type instanceof MethodType) methodType = (MethodType) type; else { methodType = null; if (!MethodHandle.class.equals(type)) throw new IllegalArgumentException(type.toString()); } List<MethodHandle> getterList = List.of(getters); MethodHandle handle; //依据 method 名称,解决对应的逻辑,别离对应了 equals(),hashCode(),toString() 的实现 switch (methodName) { case "equals": if (methodType != null && !methodType.equals(MethodType.methodType(boolean.class, recordClass, Object.class))) throw new IllegalArgumentException("Bad method type: " + methodType); handle = makeEquals(recordClass, getterList); return methodType != null ? new ConstantCallSite(handle) : handle; case "hashCode": if (methodType != null && !methodType.equals(MethodType.methodType(int.class, recordClass))) throw new IllegalArgumentException("Bad method type: " + methodType); handle = makeHashCode(recordClass, getterList); return methodType != null ? new ConstantCallSite(handle) : handle; case "toString": if (methodType != null && !methodType.equals(MethodType.methodType(String.class, recordClass))) throw new IllegalArgumentException("Bad method type: " + methodType); List<String> nameList = "".equals(names) ? List.of() : List.of(names.split(";")); if (nameList.size() != getterList.size()) throw new IllegalArgumentException("Name list and accessor list do not match"); handle = makeToString(recordClass, getterList, nameList); return methodType != null ? new ConstantCallSite(handle) : handle; default: throw new IllegalArgumentException(methodName); } }
其中,toString() 办法 的外围实现逻辑,就要看case "toString"
这一分支了,外围逻辑是makeToString(recordClass, getterList, nameList)
:
private static MethodHandle makeToString(Class<?> receiverClass, //所有的 getter 办法 List<MethodHandle> getters, //所有的 field 名称 List<String> names) { assert getters.size() == names.size(); int[] invArgs = new int[getters.size()]; Arrays.fill(invArgs, 0); MethodHandle[] filters = new MethodHandle[getters.size()]; StringBuilder sb = new StringBuilder(); //先拼接类名称[ sb.append(receiverClass.getSimpleName()).append("["); for (int i=0; i<getters.size(); i++) { MethodHandle getter = getters.get(i); // (R)T MethodHandle stringify = stringifier(getter.type().returnType()); // (T)String MethodHandle stringifyThisField = MethodHandles.filterArguments(stringify, 0, getter); // (R)String filters[i] = stringifyThisField; //之后拼接 field 名称=值 sb.append(names.get(i)).append("=%s"); if (i != getters.size() - 1) sb.append(", "); } sb.append(']'); String formatString = sb.toString(); MethodHandle formatter = MethodHandles.insertArguments(STRING_FORMAT, 0, formatString) .asCollector(String[].class, getters.size()); // (R*)String if (getters.size() == 0) { // Add back extra R formatter = MethodHandles.dropArguments(formatter, 0, receiverClass); } else { MethodHandle filtered = MethodHandles.filterArguments(formatter, 0, filters); formatter = MethodHandles.permuteArguments(filtered, MethodType.methodType(String.class, receiverClass), invArgs); } return formatter;}
同理,hashcode()
实现是:
private static MethodHandle makeHashCode(Class<?> receiverClass, List<MethodHandle> getters) { MethodHandle accumulator = MethodHandles.dropArguments(ZERO, 0, receiverClass); // (R)I // 对于每一个 field,找到对应的 hashcode 办法,取 哈希值,最初组合在一起 for (MethodHandle getter : getters) { MethodHandle hasher = hasher(getter.type().returnType()); // (T)I MethodHandle hashThisField = MethodHandles.filterArguments(hasher, 0, getter); // (R)I MethodHandle combineHashes = MethodHandles.filterArguments(HASH_COMBINER, 0, accumulator, hashThisField); // (RR)I accumulator = MethodHandles.permuteArguments(combineHashes, accumulator.type(), 0, 0); // adapt (R)I to (RR)I } return accumulator;}
同理,equals()
实现是:
private static MethodHandle makeEquals(Class<?> receiverClass, List<MethodHandle> getters) { MethodType rr = MethodType.methodType(boolean.class, receiverClass, receiverClass); MethodType ro = MethodType.methodType(boolean.class, receiverClass, Object.class); MethodHandle instanceFalse = MethodHandles.dropArguments(FALSE, 0, receiverClass, Object.class); // (RO)Z MethodHandle instanceTrue = MethodHandles.dropArguments(TRUE, 0, receiverClass, Object.class); // (RO)Z MethodHandle isSameObject = OBJECT_EQ.asType(ro); // (RO)Z MethodHandle isInstance = MethodHandles.dropArguments(CLASS_IS_INSTANCE.bindTo(receiverClass), 0, receiverClass); // (RO)Z MethodHandle accumulator = MethodHandles.dropArguments(TRUE, 0, receiverClass, receiverClass); // (RR)Z //比照两个对象的每个 field 的 getter 获取的值是否一样,对于援用类型通过 Objects.equals 办法,对于原始类型间接通过 == for (MethodHandle getter : getters) { MethodHandle equalator = equalator(getter.type().returnType()); // (TT)Z MethodHandle thisFieldEqual = MethodHandles.filterArguments(equalator, 0, getter, getter); // (RR)Z accumulator = MethodHandles.guardWithTest(thisFieldEqual, accumulator, instanceFalse.asType(rr)); } return MethodHandles.guardWithTest(isSameObject, instanceTrue, MethodHandles.guardWithTest(isInstance, accumulator.asType(ro), instanceFalse)); }
我在参加 掘金2021年度人气榜单,麻烦大家帮我投出贵重一票,谢谢