Действительно ли записи 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-битные метаданные, поэтому массив объектов будет потреблять больше памяти, чем массив записей, поскольку метаданные будут прикреплены только к ссылке на массив, а не к каждой записи/структуре. Кроме того, преимущество должно заключаться в способе управления памятью записей из сборщика мусора, поскольку она является фиксированной и непрерывной. Это то, что я понимаю, если кто-то может подтвердить или добавить дополнительную информацию, это будет очень полезно. Спасибо

Другие вопросы по тегам