Các bản ghi và mảng Java 14


11

Cho mã sau:

public static void main(String[] args) {
    record Foo(int[] ints){}

    var ints = new int[]{1, 2};
    var foo = new Foo(ints);
    System.out.println(foo); // Foo[ints=[I@6433a2]
    System.out.println(new Foo(new int[]{1,2}).equals(new Foo(new int[]{1,2}))); // false
    System.out.println(new Foo(ints).equals(new Foo(ints))); //true
    System.out.println(foo.equals(foo)); // true
}

Có vẻ như, rõ ràng, của mảng đó toString, equalscác phương pháp được sử dụng (thay vì phương pháp tĩnh, Arrays::equals, Arrays::deepEquals hoặc Array::toString).

Vì vậy, tôi đoán Bản ghi Java 14 ( JEP 359 ) không hoạt động tốt với các mảng, các phương thức tương ứng phải được tạo bằng IDE (ít nhất là trong IntelliJ, theo mặc định tạo ra các phương thức "hữu ích", tức là chúng sử dụng các phương thức tĩnh trong Arrays).

Hoặc có giải pháp nào khác không?


3
Làm thế nào về việc sử dụng Listthay vì một mảng?
Oleg

Tôi không hiểu tại sao các phương thức đó phải được tạo bằng IDE? Tất cả các phương pháp sẽ có thể được tạo ra bằng tay.
NomadMaker

1
Các toString(), equals()hashCode()phương pháp của một kỷ lục được thực hiện sử dụng một tài liệu tham khảo invokedynamic. . Nếu chỉ có lớp tương đương được biên dịch có thể gần với những gì phương thức Arrays.deepToStringthực hiện trong phương thức nạp chồng riêng của nó ngày nay, thì nó có thể đã giải quyết cho các trường hợp nguyên thủy.
Naman

1
Để thứ hai lựa chọn thiết kế để thực hiện, đối với một cái gì đó không cung cấp triển khai các phương thức này, thì đó không phải là ý tưởng tồi Object, vì điều đó cũng có thể xảy ra với các lớp do người dùng định nghĩa. ví dụ bằng không chính xác
Naman

1
@Naman Sự lựa chọn sử dụng invokedynamichoàn toàn không liên quan gì đến việc lựa chọn ngữ nghĩa; indy là một chi tiết thực hiện thuần túy ở đây. Trình biên dịch có thể đã phát ra mã byte để làm điều tương tự; đây chỉ là một cách hiệu quả và linh hoạt hơn để đạt được điều đó. Nó đã được thảo luận rộng rãi trong quá trình thiết kế các bản ghi xem có nên sử dụng một ngữ nghĩa bình đẳng nhiều sắc thái hơn (như bình đẳng sâu cho các mảng) hay không, nhưng điều này hóa ra gây ra nhiều vấn đề hơn so với giải quyết.
Brian Goetz

Câu trả lời:


18

Các mảng Java đặt ra một số thách thức cho các bản ghi và chúng đã thêm một số ràng buộc cho thiết kế. Mảng là có thể thay đổi, và ngữ nghĩa bình đẳng của chúng (được kế thừa từ Object) là theo bản sắc, không phải nội dung.

Vấn đề cơ bản với ví dụ của bạn là bạn muốn rằng equals()trên mảng có nghĩa là bình đẳng nội dung, chứ không phải là đẳng thức tham chiếu. Các ngữ nghĩa (mặc định) cho equals()các bản ghi dựa trên sự bình đẳng của các thành phần; trong ví dụ của bạn, hai Foobản ghi chứa các mảng riêng biệt khác nhau và bản ghi hoạt động chính xác. Vấn đề là bạn chỉ muốn so sánh bình đẳng là khác nhau.

Điều đó nói rằng, bạn có thể khai báo một bản ghi với ngữ nghĩa mà bạn muốn, nó chỉ cần nhiều công việc hơn và bạn có thể cảm thấy như là quá nhiều công việc. Đây là một bản ghi làm những gì bạn muốn:

record Foo(String[] ss) {
    Foo { ss = ss.clone(); }
    String[] ss() { return ss.clone(); }
    boolean equals(Object o) { 
        return o instanceof Foo 
            && Arrays.equals(((Foo) o).ss, ss);
    }
    int hashCode() { return Objects.hash(Arrays.hashCode(ss)); }
}

Những gì nó làm là một bản sao phòng thủ trên đường vào (trong hàm tạo) và trên đường ra (trong bộ truy cập), cũng như điều chỉnh ngữ nghĩa bình đẳng để sử dụng nội dung của mảng. Điều này hỗ trợ cho bất biến, được yêu cầu trong siêu lớp java.lang.Record, rằng "tách một bản ghi thành các thành phần của nó và tái cấu trúc các thành phần thành một bản ghi mới, mang lại một bản ghi bằng nhau."

Bạn cũng có thể nói "nhưng đó là quá nhiều công việc, tôi muốn sử dụng hồ sơ vì vậy tôi đã không phải gõ tất cả những thứ đó." Nhưng, các bản ghi không phải chủ yếu là một công cụ cú pháp (mặc dù chúng dễ chịu hơn về mặt cú pháp), chúng là một công cụ ngữ nghĩa: các bản ghi là bộ dữ danh nghĩa . Hầu hết thời gian, cú pháp nhỏ gọn cũng mang lại ngữ nghĩa mong muốn, nhưng nếu bạn muốn các ngữ nghĩa khác nhau, bạn phải làm thêm một số công việc.


6
Ngoài ra, đó là một lỗi phổ biến của những người muốn rằng sự bình đẳng mảng là do nội dung cho rằng không ai muốn có sự bình đẳng mảng bằng cách tham chiếu. Nhưng điều đó chỉ đơn giản là không đúng sự thật; chỉ là không có câu trả lời nào phù hợp với mọi trường hợp. Đôi khi bình đẳng tham chiếu là chính xác những gì bạn muốn.
Brian Goetz

9

List< Integer > cách giải quyết

Giải pháp thay thế: Sử dụng a Listof Integerobject ( List< Integer >) thay vì mảng primitive ( int[]).

Trong ví dụ này, tôi khởi tạo một danh sách không xác định được của lớp không xác định bằng cách sử dụng List.oftính năng được thêm vào Java 9. Bạn cũng có thể sử dụng ArrayList, cho một danh sách có thể sửa đổi được hỗ trợ bởi một mảng.

package work.basil.example;

import java.util.List;

public class RecordsDemo
{
    public static void main ( String[] args )
    {
        RecordsDemo app = new RecordsDemo();
        app.doIt();
    }

    private void doIt ( )
    {

        record Foo(List < Integer >integers)
        {
        }

        List< Integer > integers = List.of( 1 , 2 );
        var foo = new Foo( integers );

        System.out.println( foo ); // Foo[integers=[1, 2]]
        System.out.println( new Foo( List.of( 1 , 2 ) ).equals( new Foo( List.of( 1 , 2 ) ) ) ); // true
        System.out.println( new Foo( integers ).equals( new Foo( integers ) ) ); // true
        System.out.println( foo.equals( foo ) ); // true
    }
}

Mặc dù không phải lúc nào cũng nên chọn List trên một mảng và dù sao
Naman

@Naman Tôi chưa bao giờ đưa ra yêu sách về việc trở thành một ý tưởng tốt của người Bỉ. Câu hỏi theo nghĩa đen yêu cầu các giải pháp khác. Tôi đã cung cấp một. Và tôi đã làm như vậy một giờ trước khi các ý kiến ​​bạn liên kết.
Basil Bourque

5

Giải pháp thay thế: Tạo mộtIntArraylớp và bọcint[].

record Foo(IntArray ints) {
    public Foo(int... ints) { this(new IntArray(ints)); }
    public int[] getInts() { return this.ints.get(); }
}

Không hoàn hảo, bởi vì bây giờ bạn phải gọi foo.getInts()thay vì foo.ints(), nhưng mọi thứ khác hoạt động theo cách bạn muốn.

public final class IntArray {
    private final int[] array;
    public IntArray(int[] array) {
        this.array = Objects.requireNonNull(array);
    }
    public int[] get() {
        return this.array;
    }
    @Override
    public int hashCode() {
        return Arrays.hashCode(this.array);
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null || getClass() != obj.getClass())
            return false;
        IntArray that = (IntArray) obj;
        return Arrays.equals(this.array, that.array);
    }
    @Override
    public String toString() {
        return Arrays.toString(this.array);
    }
}

Đầu ra

Foo[ints=[1, 2]]
true
true
true

1
Điều đó không tương đương với việc nói sử dụng một lớp và không phải là một bản ghi trong trường hợp như vậy sao?
Naman

1
@Naman Hoàn toàn không, bởi vì bạn recordcó thể bao gồm nhiều trường và chỉ có (các) trường mảng được gói như thế này.
Andreas

Tôi nhận thấy rằng bạn đang cố gắng thực hiện về khả năng sử dụng lại, nhưng sau đó có các lớp sẵn có như Listcung cấp loại gói mà người ta có thể tìm kiếm với giải pháp như đề xuất ở đây. Hoặc bạn có nghĩ rằng đó có thể là một chi phí cho các trường hợp sử dụng như vậy không?
Naman

3
@Naman Nếu bạn muốn lưu trữ một int[], thì a List<Integer>không giống nhau, vì ít nhất hai lý do: 1) Danh sách sử dụng nhiều bộ nhớ hơn và 2) Không có chuyển đổi tích hợp giữa chúng. Integer[]🡘 List<Integer>là khá dễ dàng ( toArray(...)Arrays.asList(...)), nhưng int[]🡘 List<Integer>mất làm việc nhiều hơn, và việc chuyển đổi cần có thời gian, vì vậy nó của một cái gì đó bạn không muốn làm tất cả thời gian. Nếu bạn có int[]và muốn lưu trữ nó trong một bản ghi (với những thứ khác, nếu không thì tại sao lại sử dụng một bản ghi) và cần nó như một bản ghi int[], sau đó chuyển đổi mỗi khi bạn cần nó là sai.
Andreas

Điểm tốt thực sự. Tôi có thể mặc dù nếu bạn cho phép nitpicking hỏi chúng ta vẫn thấy những lợi ích gì Arrays.equals, Arrays.toStringqua việc Listtriển khai được sử dụng trong khi thay thế int[]như được đề xuất trong câu trả lời khác. (Giả sử cả hai đều là cách giải quyết.)
Naman
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.