Java 14のレコードは、同様のクラス宣言よりも実際にメモリを節約しますか、それとも構文糖のようなものですか?


8

Java 14のレコードは、実際には同様のデータクラスよりも少ないメモリを使用することを期待しています。

それらか、メモリ使用量は同じですか?


6
私がそれを正しく理解した場合、コンパイラは、アクセサ、インスタンス変数、必要なコンストラクタ、およびtoString、hashCode、equalsメソッドを使用してRecordを拡張する最終クラスを生成します。したがって、使用されるメモリは非常に似ていると思います。もちろん、ソースコードはより少ないメモリを使用します;)
lugiorgi

4
メモリの節約はどこから来ると思いますか?もちろん、すべてのコンポーネントを保存する必要があります。
ブライアンゲッツ

@BrianGoetzわかりました。次の質問に答えてもかまわない場合- バイトコード表現とそこで使用されるinvokedynamic定数の違いについて疑問に思っていました。(JDKの内外でこれらすべての定数の値を見つける方法はありますか?)ここで理解すべき詳細がたくさんある場合は、ここで別のQ&Aを作成できれば幸いです。
ナマン

2
invokedynamicコンパイル時に静的に生成するのではなく、Objectメソッド(equals、hashCode)の実装を遅延生成するために使用します。
ブライアンゲッツ

回答:


7

@lugiorgiによって実行される基本的な分析に加えて、バイトコードの分析で思いつく可能性がある同様の顕著な違いはtoStringequalsおよびの実装にあり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

:生成されたアクセサーとコンストラクターのバイトコードで確認できることは、これらは両方の表現で同じであるため、ここでもデータから除外されています。


1

私はいくつかの迅速で汚いテストをしました

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の実装の違いによるものです。

たぶん誰かがいくつかのバイトコードを掘ることができます...


0

正しい、私は[@lugiorgi]と一致し、[@Naman]、レコードと同等のクラスとの間に発生するバイトコードの唯一の違いは、メソッドの実装にありますtoStringequalsそしてhashCode。レコードクラスの場合は、クラスの同じブートストラップメソッドへの動的(インディ)呼び出しを使用して実装java.lang.runtime.ObjectMethodsされます(レコードプロジェクトに新しく追加されます)。これら3つの方法であるという事実toStringequalsそしてhashCode同じブートストラップメソッドを呼び出すには、3種類のブートストラップメソッドを呼び出すよりも、クラスファイル内のより多くのスペースを節約できます。そしてもちろん、他の回答ですでに示したように、明白なバイトコードを生成するよりも多くのスペースを節約します

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.