在之前的 JEP 尝鲜系列中,咱们介绍了 Java Project Valhalla 以及 Java 值类型,通过 Java 14,15,16 的一直开发优化反馈,终于 Java 16 咱们迎来了 Java 值类型的最终版设计,能够正式在生产应用 Java 值类型相干 API 也就是 Record 这个类了。

相干材料:

  • Project Valhalla
  • JEP 169: Value Objects
  • JEP 218: Generics over Primitive Types
  • JEP 359: Records(Preview)
  • JEP 384: Records (Second Preview)
  • JEP 395: Records

然而,应用这个值类型 Record 代替原有的所有 Pojo 类,会遇到很多问题。这些问题包含:

  1. 因为值类型没有原来一般 Object 的对象头等信息,所以对于一些 Object 的个性是不兼容的。
  2. 咱们目前应用 Java 开发不可能不应用很多三方 jar 包,各种库。这些库中应用的 Pojo 类型并没有应用值类型。不过,不必太放心,只有这些开源库还比拟沉闷,那么肯定早晚会兼容值类型的。

Record 的产生背景

Record 要解决的问题最次要的一点就是,让Java适应古代硬件:在 Java 语言公布之初一次内存拜访和一次数字计算的耗费工夫是差不多的,然而当初,一次内存拜访耗时大略是一次数值计算的 200 ~ 1000 倍。从语言设计上来说,也就是间接拜访带来的通过指针获取的须要操作的内存,对于整体性能影响很大。

Java 是基于对象的语言,也就是说,Java 是一种基于指针的间接援用的语言。这个基于指针的个性,给每个对象带来了惟一标识性。例如判断两个 Object 的 ==,其实判断的是两个对象的内存绝对映射地址是否雷同,只管两个对象的 field 齐全一样,他们的内存地址也不同。同时这个个性也给对象带来了多态性易变性还有的个性。然而,并不是所有对象都须要这种个性。

因为指针与间接拜访带来了性能瓶颈,Java 筹备对于不须要以上提到的个性的对象移除这些个性。于是乎, Record 呈现了。

疾速上手 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());//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.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)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));    }

微信搜寻“我的编程喵”关注公众号,每日一刷,轻松晋升技术,斩获各种offer