Quickly get started with the Record class

Let’s start with a simple example of declaring a user Record.

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

After writing this code, the default elements and method implementations of the Record class include:

  1. The component specified by the Record header (int id, String name, int age), and these elements are final.
  2. By default, record has only one constructor, which contains all elements.
  3. Each element of record has a getter (but this getter is not getXXX (), but is directly named by the variable name, so use serialization framework, DAO framework should pay attention to this)
  4. Implement good hashCode(), equals(), toString() methods (by automatically generating bytecode on the implementation of hashCode(), equals(), toString() methods at compile time).

Let’s use this 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()); / / 3739156Copy the code

How is the structure of Record implemented

After compilation, the bytecode of the related fields and methods is inserted

There are two ways to view the bytecode in the above example. One is to use the javap -v user. class command to view the literal version of the bytecode.

// omit the file header, file constant pool part {//public constructor, all attributes as arguments, And give each Field assignment public com. Making the hashzhang. Basetest. The 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 method 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 // The core implementation is this invokedynamic, which we'll look at later 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 int hashCode(); public final int hashCode(); descriptor: ()I flags: (0x0011) ACC_PUBLIC, ACC_FINAL Code: stack=1, locals=1, args_size=1 0: Aload_0 // The core implementation is this invokedynamic, which we'll look at later 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 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 // The core implementation is this Invokedynamic, which we'll look at 2 later: 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; // 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 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 getter for age 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 // Here are the methods and parameters that invokeDynamic invokes. We'll look at 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/MethodHandlesCopy the code

The other option is to use the IDE’s jclasslib plugin. I recommend using this method.

Automatically generated private final fields

Automatically generated full attribute constructor

Automatically generated public getter methods

Automatically generated hashCode(), equals(), toString() methods

At the heart of these methods is Invokedynamic:

It looks like calling another method. Isn’t there a performance cost associated with this indirect call? JVM developers have already figured this out. Let’s look at Invokedynamic first.

Context in which invokeDynamic is generated

Java was originally a statically typed language, meaning that the bulk of its type checking is done primarily at compile time rather than run time. The bytecode instruction Invokedynamic was introduced in Java 7 in order to be compatible with dynamic typing syntax and for the JVM to be compatible with dynamic languages (JVMS were not designed to run only Java). This was the basis for the later Ramda expressions and var syntax in Java 8.

Invokedynamic and MethodHandle

Invokedynamic is dependent on the use of the java.lang. Invoke package. The main purpose of this package is to provide a new mechanism for dynamically determining target methods, called MethodHandle, in addition to relying solely on symbolic references to determine the target method for a call.

MethodHandle allows you to dynamically retrieve the method you want to call. This is similar to Java Reflection, but you need MethodHandle to be efficient for the following reasons: Reflection is just a complementary implementation of Reflection in the Java language, with no consideration for efficiency, especially since the JIT is basically unable to optimize for such Reflection calls. MethodHandle is more like a simulation of method instruction calls to bytecode, and can be optimized by the JIT if used appropriately, for example by declaring method references to 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();
Copy the code

Automatically generated implementations of toString(), HashCode (), equals()

Bytecode shows that inCokeDynamic actually calls the #0 method in BoostrapMethods:

0 aload_0
1 invokedynamic #24 <hashCode, BootstrapMethods #0>
6 ireturn
Copy the code

The Bootstap method table includes:

BootstrapMethods: / / call the actual is Java lang. Runtime. ObjectMethods boostrap method 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; // Declare MethodHandles.Lookup final.  // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandlesCopy the code

From here, we can see, in fact the toString () call is Java. Lang. Runtime. The bootstap ObjectMethods () method. The core code is 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; 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); }}Copy the code

MakeToString (recordClass, getterList, nameList) makeToString(recordClass, getterList, nameList)

private static MethodHandle makeToString(Class<? > receiverClass, // All getters List<MethodHandle> getters, // All field names 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(); / / joining together first class name [sb. Append (receiverClass. GetSimpleName ()), append (" ["); the for (int I = 0; I < getters. The 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; // Add field name = value 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; }Copy the code

Similarly, the implementation of hashCode () is:

private static MethodHandle makeHashCode(Class<? > receiverClass, List<MethodHandle> getters) { MethodHandle accumulator = MethodHandles.dropArguments(ZERO, 0, receiverClass); // (R)I // For each field, find the corresponding hashCode method, take the hash value, and combine it together. 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; }Copy the code

Similarly, equals() implements:

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 // Compare the getters for each field of two Objects to see if the getters for each field are the same, using objects.equals for reference types and directly using == for primitive types. 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)); }Copy the code

I am participating in the Denver Nuggets 2021 popularity list, please help me to vote, thank you