我希望 Java 14 记录实际上比类似的数据类使用更少的内存。
他们还是使用相同的内存?
添加到@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
注意:根据我在生成的访问器和构造器字节码上观察到的情况,它们对于表示形式都是相似的,因此也被排除在此处的数据之外。
我做了一些快速而肮脏的测试,如下
public record PersonRecord(String firstName, String lastName) {}
对比
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 实现。
也许有人可以做一些字节码挖掘......
正确,我同意 [@lugiorgi] 和 [@Naman],记录和等效类之间生成的字节码的唯一区别在于方法的实现toString
:equals
和hashCode
. 在记录类的情况下,使用调用动态(indy)指令来实现类的相同引导方法:(java.lang.runtime.ObjectMethods
新添加到记录项目中)。与调用 3 个不同的引导方法相比,这三个方法、toString
和equals
调用hashCode
相同的引导方法在类文件中节省了更多空间。当然,正如其他答案中已经显示的那样,比生成明显的字节码节省更多空间
Java 中的每个对象都有 64 位元数据,因此对象数组将比记录数组消耗更多内存,因为元数据将仅附加到数组引用中,而不是每个记录/结构中。此外,优势应该在于可以从垃圾收集器管理记录内存的方式,因为它是固定且连续的。这就是我的理解,如果有人可以确认或添加额外的信息将非常有用。谢谢