Các bản ghi Java 14 có thực sự tiết kiệm bộ nhớ trong một khai báo lớp tương tự hay chúng giống như đường cú pháp hơn?


8

Tôi hy vọng rằng các bản ghi Java 14 thực sự sử dụng ít bộ nhớ hơn một lớp dữ liệu tương tự.

Họ hay là sử dụng bộ nhớ giống nhau?


6
Nếu tôi hiểu chính xác thì trình biên dịch sẽ tạo một bản ghi mở rộng lớp cuối cùng với các hàm truy cập, các biến đối tượng, hàm tạo cần thiết và các phương thức toString, hashCode và bằng. Vì vậy, tôi cho rằng bộ nhớ được sử dụng sẽ rất giống nhau. Tất nhiên mã nguồn sẽ sử dụng ít bộ nhớ hơn;)
lugiorgi

4
Bạn nghĩ tiết kiệm bộ nhớ sẽ đến từ đâu? Họ rõ ràng vẫn sẽ phải lưu trữ tất cả các thành phần.
Brian Goetz

@BrianGoetz Điều đó được hiểu. Nếu bạn không ngại trả lời một câu hỏi tiếp theo - tôi đã tự hỏi về sự khác biệt trong biểu diễn mã byte và hằng số inv invocate được sử dụng trong đó. (Có cách nào để tìm giá trị cho tất cả các hằng số này trong hoặc ngoài JDK không?). Nếu có một lượng chi tiết tốt được hiểu ở đây, tôi sẽ rất vui khi tạo một câu hỏi và trả lời khác ở đây.
Naman

2
Chúng tôi sử dụng invokedynamicđể lười biếng tạo ra các triển khai của các phương thức Object (bằng, hashCode) thay vì tạo chúng một cách tĩnh tại thời gian biên dịch.
Brian Goetz

Câu trả lời:


7

Để thêm vào các phân tích cơ bản thực hiện bởi @lugiorgi và một sự khác biệt đáng chú ý tương tự mà tôi có thể đưa ra phân tích mã byte, là trong việc thực hiện toString, equalshashcode.

Một mặt, lớp được sử dụng trước đây với ObjectAPI lớp bị ghi đè trông giống như

public class City {
    private final Integer id;
    private final String name;
    // all-args, toString, getters, equals, and hashcode
}

tạo mã byte như sau

 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

Mặt khác, đại diện hồ sơ cho cùng

record CityRecord(Integer id, String name) {}

tạo ra mã byte ít như

 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

Lưu ý : Đối với những gì tôi có thể quan sát trên mã byte của trình truy cập và hàm tạo được tạo, chúng giống nhau cho cả biểu diễn và do đó cũng bị loại khỏi dữ liệu ở đây.


1

Tôi đã làm một số thử nghiệm nhanh và bẩn với sau

public record PersonRecord(String firstName, String lastName) {}

so với

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 +
                "]";
    }
}

Tệp bản ghi đã biên dịch lên tới 1.485 byte, lớp thành 1.643 byte. Sự khác biệt về kích thước có thể đến từ các triển khai bằng / toString / hashCode khác nhau.

Có lẽ ai đó có thể thực hiện một số hoạt động đào mã byte ...


0

đúng, tôi đồng ý với [@lugiorgi] và [@Naman], sự khác biệt duy nhất trong bytecode tạo giữa một kỷ lục và các lớp tương đương là trong việc thực hiện các phương pháp: toString, equalshashCode. Mà trong trường hợp một lớp bản ghi được triển khai bằng cách sử dụng một lệnh gọi động (indy) cho cùng một phương thức bootstrap trong lớp: java.lang.runtime.ObjectMethods(được thêm mới trong dự án bản ghi). Thực tế là ba phương pháp này, toString, equalshashCode, invoke bootstrap cùng một phương pháp tiết kiệm không gian hơn trong tập tin lớp hơn gọi 3 phương pháp bootstraps khác nhau. Và tất nhiên như đã được hiển thị trong các câu trả lời khác, tiết kiệm nhiều không gian hơn so với việc tạo mã byte rõ ràng

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.