14 catatan dan array Java

11

Diberikan kode berikut:

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
}

Tampaknya, jelas bahwa array toString, equalsmetode digunakan (bukan metode statis Arrays::equals,, Arrays::deepEquals atau Array::toString).

Jadi saya kira Java 14 Records ( JEP 359 ) tidak berfungsi dengan baik dengan array, metode masing-masing harus dihasilkan dengan IDE (yang setidaknya di IntelliJ, secara default menghasilkan metode "berguna", yaitu mereka menggunakan metode statis dalam Arrays).

Atau adakah solusi lain?

pengguna140547
sumber
3
Bagaimana kalau menggunakan Listbukan array?
Oleg
Saya tidak mengerti mengapa metode seperti itu harus dihasilkan dengan IDE? Semua metode harus dapat dihasilkan dengan tangan.
NomadMaker
1
The toString(), equals()dan hashCode()metode rekor diimplementasikan menggunakan referensi invokedynamic. . Jika hanya kelas yang dikompilasi setara bisa lebih dekat dengan apa yang dilakukan metode Arrays.deepToStringini dalam metode kelebihan beban pribadinya hari ini, itu mungkin telah dipecahkan untuk kasus primitif.
Naman
1
Yang kedua adalah pilihan desain untuk implementasi, untuk sesuatu yang tidak menyediakan implementasi yang ditimpa metode ini, itu bukan ide yang buruk untuk kembali ke Object, karena itu bisa terjadi dengan kelas yang ditentukan pengguna juga. mis salah sama dengan
Naman
1
@Naman Pilihan untuk menggunakan invokedynamicsama sekali tidak ada hubungannya dengan pemilihan semantik; indy adalah detail implementasi murni di sini. Kompilator dapat memancarkan bytecode untuk melakukan hal yang sama; ini hanya cara yang lebih efisien dan fleksibel untuk sampai ke sana. Itu dibahas secara luas selama desain catatan apakah akan menggunakan semantik kesetaraan yang lebih bernuansa (seperti kesetaraan yang mendalam untuk array), tetapi ini ternyata menyebabkan jauh lebih banyak masalah daripada yang seharusnya dipecahkan.
Brian Goetz

Jawaban:

18

Array Java menimbulkan beberapa tantangan untuk rekaman, dan ini menambah sejumlah kendala pada desain. Array bisa berubah, dan semantik kesetaraan mereka (diwarisi dari Object) adalah dengan identitas, bukan konten.

Masalah mendasar dengan contoh Anda adalah bahwa Anda berharap bahwa equals()pada array berarti kesetaraan konten, bukan kesetaraan referensi. Semantik (default) equals()untuk catatan didasarkan pada kesetaraan komponen; dalam contoh, dua Foocatatan yang berisi array yang berbeda yang berbeda, dan catatan berperilaku dengan benar. Masalahnya adalah Anda hanya berharap perbandingan kesetaraannya berbeda.

Yang mengatakan, Anda dapat mendeklarasikan catatan dengan semantik yang Anda inginkan, itu hanya membutuhkan lebih banyak pekerjaan, dan Anda mungkin merasa terlalu banyak bekerja. Berikut catatan yang melakukan apa yang Anda inginkan:

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)); }
}

Apa yang dilakukan adalah salinan defensif di jalan (di konstruktor) dan di jalan keluar (di accessor), serta menyesuaikan semantik kesetaraan untuk menggunakan konten array. Ini mendukung invarian, diperlukan dalam superclass java.lang.Record, yang "memisahkan catatan ke dalam komponennya, dan merekonstruksi komponen menjadi catatan baru, menghasilkan rekor yang sama."

Anda mungkin berkata, "Tapi itu terlalu banyak pekerjaan, saya ingin menggunakan catatan sehingga saya tidak perlu mengetik semua itu." Tetapi, catatan bukan terutama alat sintaksis (meskipun secara sintaksis lebih menyenangkan), catatan adalah alat semantik: catatan adalah tupel nominal . Sebagian besar waktu, sintaks kompak juga menghasilkan semantik yang diinginkan, tetapi jika Anda ingin semantik yang berbeda, Anda harus melakukan beberapa pekerjaan tambahan.

Brian Goetz
sumber
6
Juga, ini adalah kesalahan umum oleh orang-orang yang berharap bahwa persamaan array adalah berdasarkan konten untuk mengasumsikan bahwa tidak ada yang pernah menginginkan persamaan array dengan referensi. Tetapi itu tidak benar; hanya saja tidak ada satu jawaban yang cocok untuk semua kasus. Kadang-kadang referensi kesetaraan adalah persis apa yang Anda inginkan.
Brian Goetz
9

List< Integer > solusi

Penanganan Masalah: Gunakan a Listdari Integerobjek ( List< Integer >) daripada array primitif ( int[]).

Dalam contoh ini, saya instantiate daftar kelas yang tidak ditentukan yang tidak dapat dimodifikasi dengan menggunakan List.offitur yang ditambahkan ke Java 9. Anda bisa juga menggunakan ArrayList, untuk daftar yang dapat dimodifikasi yang didukung oleh array.

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
    }
}
Basil Bourque
sumber
Namun, tidak selalu merupakan ide yang baik untuk memilih List daripada array dan kami tetap mendiskusikannya.
Naman
@Naman Saya tidak pernah membuat klaim tentang menjadi "ide bagus". Pertanyaan itu secara harfiah meminta solusi lain. Saya menyediakan satu. Dan saya melakukannya satu jam sebelum komentar yang Anda tautkan.
Basil Bourque
5

Penanganan masalah: BuatIntArraykelas dan bungkusint[].

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

Tidak sempurna, karena Anda sekarang harus menelepon foo.getInts()alih-alih foo.ints(), tetapi semua yang lain berfungsi seperti yang Anda inginkan.

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);
    }
}

Keluaran

Foo[ints=[1, 2]]
true
true
true
Andreas
sumber
1
Bukankah itu setara dengan mengatakan untuk menggunakan kelas dan bukan catatan dalam kasus seperti itu?
Naman
1
@Naman Tidak sama sekali, karena Anda recorddapat terdiri dari banyak bidang, dan hanya bidang array yang dibungkus seperti ini.
Andreas
Saya mendapatkan poin yang Anda coba buat dalam hal usabilitas, tetapi kemudian ada kelas inbuilt seperti Listyang menyediakan jenis pembungkus yang mungkin dicari seseorang dengan solusi seperti yang diusulkan di sini. Atau apakah Anda menganggap itu bisa menjadi overhead untuk kasus penggunaan seperti itu?
Naman
3
@Naman Jika Anda ingin menyimpan int[], maka a List<Integer>tidak sama, untuk setidaknya dua alasan: 1) Daftar ini menggunakan lebih banyak memori, dan 2) Tidak ada konversi bawaan di antara mereka. Integer[]🡘 List<Integer>cukup mudah ( toArray(...)dan Arrays.asList(...)), tetapi int[]🡘 List<Integer>membutuhkan lebih banyak pekerjaan, dan konversi membutuhkan waktu, jadi itu adalah sesuatu yang tidak ingin Anda lakukan sepanjang waktu. Jika Anda memiliki int[]dan ingin menyimpannya dalam rekaman (dengan barang lain, jika tidak mengapa menggunakan catatan), dan membutuhkannya sebagai int[], lalu mengonversi setiap kali Anda membutuhkannya adalah salah.
Andreas
Poin bagus memang. Namun saya bisa jika Anda mengizinkan nitpicking menanyakan manfaat apa yang masih kita lihat Arrays.equals, Arrays.toStringatas Listimplementasi yang digunakan saat mengganti int[]seperti yang disarankan dalam jawaban lainnya. (Dengan asumsi kedua ini adalah penyelesaian masalah.)
Naman