快速上手 Record 类

我们先举一个简单例子,声明一个用户 Record。

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

这样编写代码之后,Record 类默认包含的元素和方法实现包括:

  1. record 头指定的组成元素(int id, String name, int age),并且,这些元素都是 final 的。
  2. record 默认只有一个构造器,是包含所有元素的构造器。
  3. record 的每个元素都有一个对应的 getter(但这种 getter 并不是 getxxx(),而是直接用变量名命名,所以使用序列化框架,DAO 框架都要注意这一点)
  4. 实现好的 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());//1
System.out.println(zhx.name());//zhx
System.out.println(zhx.age());//29
System.out.println(zhx.equals(ttj));//false
System.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)Vflags: (0x0001) ACC_PUBLICCode:stack=3, locals=5, args_size=40: aload_01: invokespecial #1                  // Method java/lang/Record."<init>":()V4: aload_05: lload_16: putfield      #7                  // Field id:J9: aload_010: aload_311: putfield      #13                 // Field name:Ljava/lang/String;14: aload_015: iload         417: putfield      #17                 // Field age:I20: returnLineNumberTable:line 3: 0LocalVariableTable:Start  Length  Slot  Name   Signature0      21     0  this   Lcom/github/hashzhang/basetest/User;0      21     1    id   J0      21     3  name   Ljava/lang/String;0      21     4   age   IMethodParameters:Name                           Flagsidnameage//public final 修饰的 toString 方法public final java.lang.String toString();descriptor: ()Ljava/lang/String;flags: (0x0011) ACC_PUBLIC, ACC_FINALCode:stack=1, locals=1, args_size=10: aload_0//核心实现是这个 invokedynamic,我们后面会分析1: invokedynamic #21,  0             // InvokeDynamic #0:toString:(Lcom/github/hashzhang/basetest/User;)Ljava/lang/String;6: areturnLineNumberTable:line 3: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       7     0  this   Lcom/github/hashzhang/basetest/User;//public final 修饰的 hashCode 方法public final int hashCode();descriptor: ()Iflags: (0x0011) ACC_PUBLIC, ACC_FINALCode:stack=1, locals=1, args_size=10: aload_0//核心实现是这个 invokedynamic,我们后面会分析1: invokedynamic #25,  0             // InvokeDynamic #0:hashCode:(Lcom/github/hashzhang/basetest/User;)I6: ireturnLineNumberTable:line 3: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       7     0  this   Lcom/github/hashzhang/basetest/User;//public final 修饰的 equals 方法public final boolean equals(java.lang.Object);descriptor: (Ljava/lang/Object;)Zflags: (0x0011) ACC_PUBLIC, ACC_FINALCode:stack=2, locals=2, args_size=20: aload_01: aload_1//核心实现是这个 invokedynamic,我们后面会分析2: invokedynamic #29,  0             // InvokeDynamic #0:equals:(Lcom/github/hashzhang/basetest/User;Ljava/lang/Object;)Z7: ireturnLineNumberTable:line 3: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       8     0  this   Lcom/github/hashzhang/basetest/User;0       8     1     o   Ljava/lang/Object;//public 修饰的 id 的 getterpublic long id();descriptor: ()Jflags: (0x0001) ACC_PUBLICCode:stack=2, locals=1, args_size=10: aload_01: getfield      #7                  // Field id:J4: lreturnLineNumberTable:line 3: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       5     0  this   Lcom/github/hashzhang/basetest/User;//public 修饰的 name 的 getterpublic java.lang.String name();descriptor: ()Ljava/lang/String;flags: (0x0001) ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: getfield      #13                 // Field name:Ljava/lang/String;4: areturnLineNumberTable:line 3: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       5     0  this   Lcom/github/hashzhang/basetest/User;//public 修饰的 age 的 getterpublic int age();descriptor: ()Iflags: (0x0001) ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: getfield      #17                 // Field age:I4: ireturnLineNumberTable:line 3: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       5     0  this   Lcom/github/hashzhang/basetest/User;
}
SourceFile: "User.java"
Record:long id;descriptor: Jjava.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:I
InnerClasses: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_0
1 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:I
InnerClasses://声明 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.ObjectMethodsbootstap() 方法。其核心代码是:
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)TMethodHandle stringify = stringifier(getter.type().returnType()); // (T)StringMethodHandle stringifyThisField = MethodHandles.filterArguments(stringify, 0, getter);    // (R)Stringfilters[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*)Stringif (getters.size() == 0) {// Add back extra Rformatter = 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)IMethodHandle hashThisField = MethodHandles.filterArguments(hasher, 0, getter);    // (R)IMethodHandle combineHashes = MethodHandles.filterArguments(HASH_COMBINER, 0, accumulator, hashThisField); // (RR)Iaccumulator = 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)ZMethodHandle instanceTrue = MethodHandles.dropArguments(TRUE, 0, receiverClass, Object.class); // (RO)ZMethodHandle isSameObject = OBJECT_EQ.asType(ro); // (RO)ZMethodHandle isInstance = MethodHandles.dropArguments(CLASS_IS_INSTANCE.bindTo(receiverClass), 0, receiverClass); // (RO)ZMethodHandle accumulator = MethodHandles.dropArguments(TRUE, 0, receiverClass, receiverClass); // (RR)Z//对比两个对象的每个 field 的 getter 获取的值是否一样,对于引用类型通过 Objects.equals 方法,对于原始类型直接通过 == for (MethodHandle getter : getters) {MethodHandle equalator = equalator(getter.type().returnType()); // (TT)ZMethodHandle thisFieldEqual = MethodHandles.filterArguments(equalator, 0, getter, getter); // (RR)Zaccumulator = MethodHandles.guardWithTest(thisFieldEqual, accumulator, instanceFalse.asType(rr));}return MethodHandles.guardWithTest(isSameObject,instanceTrue,MethodHandles.guardWithTest(isInstance, accumulator.asType(ro), instanceFalse));}

我在参与 掘金2021年度人气榜单,麻烦大家帮我投出宝贵一票,谢谢

Java Record 的一些思考 - 默认方法使用以及基于预编译生成相关字节码的底层实现相关推荐

  1. 【Java 8 新特性】Java Comparator.reversed | 倒序排序 - 默认方法

    Java Comparator.reversed | 倒序排序 - 默认方法 在 Stream.sorted 中使用 在 List.sort 中使用 在 Collections.sort 中使用 参考 ...

  2. Java 8 新特性之默认方法

    1. 简介   默认方法就是接口可以有实现方法, 而且不需要实现类去实现其方法, 默认方法的实现只需要在方法名的前面加一个default关键字即可实现默认方法. 2. 默认方法语法 public in ...

  3. java源程序是由类定义_无论Java源程序包含几个类的定义,若该源程序文件以A.java命名,编译后生成的都只有一个名为A的字节码文件...

    无论Java源程序包含几个类的定义,若该源程序文件以A.java命名,编译后生成的都只有一个名为A的字节码文件 答:× 小儿头皮静脉输液时如误注入动脉,局部表现为: 答:呈树枝状苍白 人民群众既是历史 ...

  4. java中application后缀_编译JavaApplication源程序文件将产生相应的字节码文件,这些字节码文件的扩展名为...

    编译全球第一大消场是费市. 程序面"面从布局战略个全党和全严治"四是指. 同的些共学科了当课程在着重视重视重视重视国课革中个别趋势代世的一的是的_的培和知内容能力程改差异反映发展养 ...

  5. java翻译后的文件扩展名_Java语言的源程序翻译成字节码之后的扩展名是.______。(填英文,小写)...

    影院里一排座位有30个,小明和小红两人去看电影,他们要坐在一起,并且小红坐在小明的左边,一共有______种不同 长方形是轴对称图形.______. 果园里有苹果树56棵,梨树比苹果树多18棵,橘子树 ...

  6. 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)

    作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-language- ...

  7. 超详细解读Java接口:模块通信协议以及默认方法和静态方法

    有不少学习Java的同学一直有个疑问,不仅在初学者中很普遍,连许多经验丰富的老手也很难表述清楚,那就是:Java接口到底是什么? 来看看孙鑫老师的讲解,本文干货含量拉满,这可能是距离你深入理解Java ...

  8. [转]深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)...

    以下内容转自: 作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-l ...

  9. Java Lambda(语言篇——lambda,方法引用,目标类型,默认方法,函数接口,变量捕获)

    深入理解Java 8 Lambda(语言篇--lambda,方法引用,目标类型和默认方法) 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout ...

最新文章

  1. 计算机二级考数组吗,数组-Java语言程序设计重要笔记 计算机二级考试
  2. VTK:网格之ClipClosedSurface
  3. Linux怎么调oracle存储,Linux 环境下Oracle安装与调试(四)之视图、存储过程
  4. mysql proxy 管理_ProxyMySQL的Admin管理接口
  5. macbook pro python开发_Mac打造python2 python3开发环境
  6. 用Delphi编写ASP的ActiveX
  7. python pip install syntaxerror_解决pip install xxx报错SyntaxError: invalid syntax的问题
  8. 微信小程序单选框radio使用实例
  9. win10系统云服务器配置,win10系统云服务器配置
  10. React学习笔记之三(关于setState的若干问题及其讨论)
  11. 张无忌当年究竟对她做了什么?走进爱恨情仇,探究他与她之间的瓜葛!
  12. MySQLSyntaxErrorException异常处理办法
  13. linux cat命令缩写,linux下cat命令详解
  14. 【数据库专题】“第一幕”——《狗叫江湖》之数据库系统概论(续集)【蒸滴很c】
  15. 《java并发编程实战》- 关于this引用溢出
  16. 日常絮叨: 01 首秀
  17. Docker下载加速:Docker镜像下载加速、pip 下载加速、apt 下载加速
  18. 很多网站、APP 前段时间一下都变灰了。 先来感受一下变灰后的效果。
  19. BPRetriever:批量Sci文献检索下载工具(图形界面版)
  20. 多维分析预汇总应该怎样做才管用?

热门文章

  1. 2015互联网金融安全论坛北京站圆满举行
  2. 图片验证码自动识别,使用tess4j进行验证码自动识别(java实现)
  3. 关于EditText输入数字密码
  4. 手动管理采购订单周期的挑战以及如何应对
  5. 加密芯片具体是要保护什么
  6. Java中进行日期先后顺序的比较
  7. 自动驾驶/智能网联矿山运输示范应用现状
  8. python爬虫好友图片_用itchat库爬取你所有微信好友的头像,并合成一张大图
  9. 原神抽卡模拟器,unity制作(由于没有获得作者的视频授权,不会发布软件,只展示算法与开发等,效果图在个人主页类有资源下载,不会上传视频)
  10. 电脑耳机有电流声如何解决?