Действительно ли записи Java экономят память по сравнению с аналогичным объявлением класса или они больше похожи на синтаксический сахар?
Я надеюсь, что записи Java 14 действительно используют меньше памяти, чем аналогичный класс данных.
Они или память используют то же самое?
4 ответа
Чтобы добавить к основному анализу, выполняемому @lugiorgi, и аналогичное заметное различие, которое я мог бы придумать, анализируя байтовый код, заключается в реализацииtoString
, equals
а также hashcode
.
С одной стороны, существующий класс с переопределенным Object
API класса, выглядящие как
public class City {
private final Integer id;
private final String name;
// all-args, toString, getters, equals, and hashcode
}
производит следующий байт-код
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
С другой стороны, представление записи для того же
record CityRecord(Integer id, String name) {}
производит байт-код меньше, чем
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
Примечание. Что я мог наблюдать в сгенерированных средствах доступа и байтовом коде конструктора, они одинаковы как для представления, так и, следовательно, здесь также исключены из данных.
Правильно , я согласен с [@lugiorgi] и [@Naman], единственная разница в сгенерированном байт-коде между записью и эквивалентным классом заключается в реализации методов: toString
, equals
а также hashCode
. Которые в случае класса записи реализуются с использованием инструкции invoke dynamic (indy) для того же метода начальной загрузки в классе:java.lang.runtime.ObjectMethods
(только что добавлено в проект записей). Дело в том, что эти три метода,toString
, equals
а также hashCode
, вызов одного и того же метода начальной загрузки экономит больше места в файле класса, чем вызов 3 различных методов начальной загрузки. И, конечно, как уже было показано в других ответах, экономит больше места, чем создание очевидного байт-кода
Я провел быстрое и грязное тестирование со следующими
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 +
"]";
}
}
Размер скомпилированного файла записи составляет 1,475 байта, класса - 1,643 байта. Разница в размерах, вероятно, связана с разными реализациями equals/toString/hashCode.
Может быть, кто-нибудь сможет покопаться в байт-коде...
Каждый объект в java имеет 64-битные метаданные, поэтому массив объектов будет потреблять больше памяти, чем массив записей, поскольку метаданные будут прикреплены только к ссылке на массив, а не к каждой записи/структуре. Кроме того, преимущество должно заключаться в способе управления памятью записей из сборщика мусора, поскольку она является фиксированной и непрерывной. Это то, что я понимаю, если кто-то может подтвердить или добавить дополнительную информацию, это будет очень полезно. Спасибо