Espero que los registros de Java 14 realmente usen menos memoria que una clase de datos similar.
¿Son o el uso de memoria es el mismo?
fuente
Espero que los registros de Java 14 realmente usen menos memoria que una clase de datos similar.
¿Son o el uso de memoria es el mismo?
Para agregar al análisis básico realizado por @lugiorgi y una diferencia notable similar que podría llegar a analizar el código de bytes, está en la implementación de toString
, equals
y hashcode
.
Por un lado, clase utilizada anteriormente con Object
API de clase anuladas que se parecen a
public class City {
private final Integer id;
private final String name;
// all-args, toString, getters, equals, and hashcode
}
produce el código de bytes de la siguiente manera
public java.lang.String toString();
Code:
0: aload_0
1: getfield #7 // Field id:Ljava/lang/Integer;
4: aload_0
5: getfield #13 // Field name:Ljava/lang/String;
8: invokedynamic #17, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/Integer;Ljava/lang/String;)Ljava/lang/String;
13: areturn
public boolean equals(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: if_acmpne 7
5: iconst_1
6: ireturn
7: aload_1
8: ifnull 22
11: aload_0
12: invokevirtual #21 // Method java/lang/Object.getClass:()Ljava/lang/Class;
15: aload_1
16: invokevirtual #21 // Method java/lang/Object.getClass:()Ljava/lang/Class;
19: if_acmpeq 24
22: iconst_0
23: ireturn
24: aload_1
25: checkcast #8 // class edu/forty/bits/records/equals/City
28: astore_2
29: aload_0
30: getfield #7 // Field id:Ljava/lang/Integer;
33: aload_2
34: getfield #7 // Field id:Ljava/lang/Integer;
37: invokevirtual #25 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
40: ifne 45
43: iconst_0
44: ireturn
45: aload_0
46: getfield #13 // Field name:Ljava/lang/String;
49: aload_2
50: getfield #13 // Field name:Ljava/lang/String;
53: invokevirtual #31 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
56: ireturn
public int hashCode();
Code:
0: aload_0
1: getfield #7 // Field id:Ljava/lang/Integer;
4: invokevirtual #34 // Method java/lang/Integer.hashCode:()I
7: istore_1
8: bipush 31
10: iload_1
11: imul
12: aload_0
13: getfield #13 // Field name:Ljava/lang/String;
16: invokevirtual #38 // Method java/lang/String.hashCode:()I
19: iadd
20: istore_1
21: iload_1
22: ireturn
Por otro lado la representación de registro para el mismo
record CityRecord(Integer id, String name) {}
produce el bytecode tan poco como
public java.lang.String toString();
Code:
0: aload_0
1: invokedynamic #19, 0 // InvokeDynamic #0:toString:(Ledu/forty/bits/records/equals/CityRecord;)Ljava/lang/String;
6: areturn
public final int hashCode();
Code:
0: aload_0
1: invokedynamic #23, 0 // InvokeDynamic #0:hashCode:(Ledu/forty/bits/records/equals/CityRecord;)I
6: ireturn
public final boolean equals(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: invokedynamic #27, 0 // InvokeDynamic #0:equals:(Ledu/forty/bits/records/equals/CityRecord;Ljava/lang/Object;)Z
7: ireturn
Nota : Por lo que pude observar en los accesos y el código de bytes de constructor generado, son iguales tanto para la representación como, por lo tanto, también están excluidos de los datos aquí.
Hice algunas pruebas rápidas y sucias con los siguientes
public record PersonRecord(String firstName, String lastName) {}
vs.
import java.util.Objects;
public final class PersonClass {
private final String firstName;
private final String lastName;
public PersonClass(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String firstName() {
return firstName;
}
public String lastName() {
return lastName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PersonClass that = (PersonClass) o;
return firstName.equals(that.firstName) &&
lastName.equals(that.lastName);
}
@Override
public int hashCode() {
return Objects.hash(firstName, lastName);
}
@Override
public String toString() {
return "PersonRecord[" +
"firstName=" + firstName +
", lastName=" + lastName +
"]";
}
}
El archivo de registro compilado asciende a 1.475 bytes, la clase a 1.643 bytes. La diferencia de tamaño probablemente proviene de diferentes implementaciones equals / toString / hashCode.
Tal vez alguien pueda hacer una búsqueda de código de bytes ...
correcta, estoy de acuerdo con [@lugiorgi] y [@Naman], la única diferencia en el código de bytes que se genera entre un registro y la clase es equivalente en la aplicación de métodos: toString
, equals
y hashCode
. Que en el caso de una clase de registro se implementan utilizando una instrucción dinámica de invocación (indy) para el mismo método de arranque en clase: java.lang.runtime.ObjectMethods
(recién agregado en el proyecto de registros). El hecho de que estos tres métodos, toString
, equals
y hashCode
, invocar el mismo arranque método ahorra más espacio en el archivo de clase 3 de invocar métodos bootstraps diferentes. Y, por supuesto, como ya se mostró en las otras respuestas, ahorra más espacio que generar el bytecode obvio
invokedynamic
para generar perezosamente las implementaciones de métodos Object (equals, hashCode) en lugar de generarlas estáticamente en tiempo de compilación.