JavaRush /Java Blog /Random-ID /Rehat kopi #130. Cara bekerja dengan array Java dengan be...

Rehat kopi #130. Cara bekerja dengan array Java dengan benar - tips dari Oracle

Dipublikasikan di grup Random-ID
Sumber: Oracle Bekerja dengan array dapat mencakup ekspresi refleksi, generik, dan lambda. Saya baru-baru ini berbicara dengan seorang rekan yang mengembangkan di C. Percakapan beralih ke array dan cara kerjanya di Java dibandingkan dengan C. Saya merasa ini agak aneh, mengingat Java dianggap sebagai bahasa mirip C. Sebenarnya banyak persamaannya, namun ada juga perbedaannya. Mari kita mulai dengan sederhana. Rehat kopi #130.  Cara bekerja dengan benar dengan array Java - tips dari Oracle - 1

Deklarasi Array

Jika Anda mengikuti tutorial Java, Anda akan melihat bahwa ada dua cara untuk mendeklarasikan array. Yang pertama sangat mudah:
int[] array; // a Java array declaration
Anda dapat melihat perbedaannya dengan C, yang sintaksnya adalah:
int array[]; // a C array declaration
Mari kita kembali lagi ke Jawa. Setelah mendeklarasikan array, Anda perlu mengalokasikannya:
array = new int[10]; // Java array allocation
Apakah mungkin mendeklarasikan dan menginisialisasi array sekaligus? Sebenarnya tidak:
int[10] array; // NOPE, ERROR!
Namun, Anda dapat mendeklarasikan dan menginisialisasi array segera jika Anda sudah mengetahui nilainya:
int[] array = { 0, 1, 1, 2, 3, 5, 8 };
Bagaimana jika Anda tidak tahu artinya? Berikut kode yang paling sering Anda lihat untuk mendeklarasikan, mengalokasikan, dan menggunakan array int :
int[] array;
array = new int[10];
array[0] = 0;
array[1] = 1;
array[2] = 1;
array[3] = 2;
array[4] = 3;
array[5] = 5;
array[6] = 8;
...
Perhatikan bahwa saya menentukan array int , yang merupakan array tipe data primitif Java . Mari kita lihat apa yang terjadi jika Anda mencoba proses yang sama dengan array objek Java, bukan primitif:
class SomeClass {
    int val;
    // …
}
SomeClass[] array = new SomeClass[10];
array[0].val = 0;
array[1].val = 1;
array[2].val = 1;
array[3].val = 2;
array[4].val = 3;
array[5].val = 5;
array[6].val = 8;
Jika kita menjalankan kode di atas, kita akan mendapatkan pengecualian segera setelah mencoba menggunakan elemen pertama dari array. Mengapa? Meskipun array dialokasikan, setiap segmen array berisi referensi objek kosong. Jika Anda memasukkan kode ini ke dalam IDE Anda, maka secara otomatis akan mengisi .val untuk Anda, sehingga kesalahannya mungkin membingungkan. Untuk memperbaiki bug, ikuti langkah-langkah berikut:
SomeClass[] array = new SomeClass[10];
for ( int i = 0; i < array.length; i++ ) {  //new code
    array[i] = new SomeClass();             //new code
}                                           //new code
array[0].val = 0;
array[1].val = 1;
array[2].val = 1;
array[3].val = 2;
array[4].val = 3;
array[5].val = 5;
array[6].val = 8;
Tapi itu tidak elegan. Saya bertanya-tanya mengapa saya tidak dapat dengan mudah mengalokasikan array dan objek di dalam array dengan lebih sedikit kode, bahkan mungkin semuanya dalam satu baris. Untuk menemukan jawabannya, saya melakukan beberapa percobaan.

Menemukan nirwana di antara array Java

Tujuan kami adalah membuat kode dengan elegan. Mengikuti aturan “kode bersih”, saya memutuskan untuk membuat kode yang dapat digunakan kembali untuk membersihkan pola alokasi array. Inilah percobaan pertama:
public class MyArray {

    public static Object[] toArray(Class cls, int size)
      throws Exception {
        Constructor ctor = cls.getConstructors()[0];
        Object[] objects = new Object[size];
        for ( int i = 0; i < size; i++ ) {
            objects[i] = ctor.newInstance();
        }

        return objects;
    }

    public static void main(String[] args) throws Exception {
        SomeClass[] array1 = (SomeClass[])MyArray.toArray(SomeClass.class, 32); // see this
        System.out.println(array1);
    }
}
Baris kode bertanda "lihat ini" terlihat persis seperti yang saya inginkan, berkat implementasi toArray . Pendekatan ini menggunakan refleksi untuk menemukan konstruktor default untuk kelas yang disediakan dan kemudian memanggil konstruktor tersebut untuk membuat instance objek kelas tersebut. Proses ini memanggil konstruktor satu kali untuk setiap elemen array. Sangat menyenangkan! Sayangnya itu tidak berhasil. Kode dikompilasi dengan baik, tetapi menimbulkan kesalahan ClassCastException saat dijalankan. Untuk menggunakan kode ini, Anda perlu membuat array elemen Object , dan kemudian memasukkan setiap elemen array ke kelas SomeClass seperti ini:
Object[] objects = MyArray.toArray(SomeClass.class, 32);
SomeClass scObj = (SomeClass)objects[0];
...
Ini tidak elegan! Setelah lebih banyak bereksperimen, saya mengembangkan beberapa solusi menggunakan refleksi, generik, dan ekspresi lambda.

Solusi 1: Gunakan refleksi

Di sini kita menggunakan kelas java.lang.reflect.Array untuk membuat instance array dari kelas yang Anda tentukan alih-alih menggunakan kelas dasar java.lang.Object . Ini pada dasarnya adalah perubahan kode satu baris:
public static Object[] toArray(Class cls, int size) throws Exception {
    Constructor ctor = cls.getConstructors()[0];
    Object array = Array.newInstance(cls, size);  // new code
    for ( int i = 0; i < size; i++ ) {
        Array.set(array, i, ctor.newInstance());  // new code
    }
    return (Object[])array;
}
Anda dapat menggunakan pendekatan ini untuk mendapatkan array dari kelas yang diinginkan dan kemudian mengerjakannya seperti ini:
SomeClass[] array1 = (SomeClass[])MyArray.toArray(SomeClass.class, 32);
Meskipun ini bukan perubahan yang diperlukan, baris kedua telah diubah untuk menggunakan kelas refleksi Array untuk mengatur konten setiap elemen array. Ini luar biasa! Namun ada satu detail lagi yang tampaknya kurang tepat: pemeran SomeClass[] tidak terlihat bagus. Untungnya, ada solusi dengan obat generik.

Solusi 2: Gunakan obat generik

Kerangka Koleksi menggunakan generik untuk pengikatan tipe dan menghilangkan pemerannya dalam banyak operasinya. Obat generik juga dapat digunakan di sini. Mari kita ambil Java.util.List sebagai contoh .
List list = new ArrayList();
list.add( new SomeClass() );
SomeClass sc = list.get(0); // Error, needs a cast unless...
Baris ketiga pada cuplikan di atas akan menimbulkan kesalahan kecuali Anda memperbarui baris pertama seperti ini:
List<SomeClass> = new ArrayList();
Anda dapat mencapai hasil yang sama dengan menggunakan obat generik di kelas MyArray . Ini versi barunya:
public class MyArray<E> {
    public <E> E[] toArray(Class cls, int size) throws Exception {
        E[] array = (E[])Array.newInstance(cls, size);
        Constructor ctor = cls.getConstructors()[0];
        for ( int element = 0; element < array.length; element++ ) {
            Array.set(array, element, ctor.newInstance());
        }
        return arrayOfGenericType;
    }
}
// ...
MyArray<SomeClass> a1 = new MyArray(SomeClass.class, 32);
SomeClass[] array1 = a1.toArray();
Itu terlihat bagus. Dengan menggunakan obat generik dan menyertakan tipe target dalam deklarasi, tipe tersebut dapat disimpulkan dalam operasi lain. Selain itu, kode ini dapat dikurangi menjadi satu baris dengan melakukan ini:
SomeClass[] array = new MyArray<SomeClass>(SomeClass.class, 32).toArray();
Misi tercapai, bukan? Ya, kurang tepat. Ini bagus jika Anda tidak peduli konstruktor kelas mana yang Anda panggil, tetapi jika Anda ingin memanggil konstruktor tertentu, solusi ini tidak akan berhasil. Anda dapat terus menggunakan refleksi untuk menyelesaikan masalah ini, tetapi kodenya akan menjadi rumit. Untungnya, ada ekspresi lambda yang menawarkan solusi lain.

Solusi 3: Gunakan ekspresi lambda

Saya akui, saya tidak terlalu tertarik dengan ekspresi lambda sebelumnya, namun saya telah belajar untuk menghargainya. Secara khusus, saya menyukai antarmuka java.util.stream.Stream , yang menangani kumpulan objek. Aliran membantu saya mencapai nirwana array Java. Inilah upaya pertama saya menggunakan lambda:
SomeClass[] array =
    Stream.generate(() -> new SomeClass())
    .toArray(SomeClass[]::new);
Saya telah membagi kode ini menjadi tiga baris agar lebih mudah dibaca. Anda dapat melihat bahwa ia mencentang semua kotak: sederhana dan elegan, membuat array berisi instance objek, dan memungkinkan Anda memanggil konstruktor tertentu. Perhatikan parameter metode toArray : SomeClass[]::new . Ini adalah fungsi generator yang digunakan untuk mengalokasikan array dengan tipe tertentu. Namun, saat ini, kode ini memiliki masalah kecil: ia menciptakan array dengan ukuran tak terbatas. Hal ini sangat tidak optimal. Namun masalahnya dapat diselesaikan dengan memanggil metode limit :
SomeClass[] array =
    Stream.generate(() -> new SomeClass())
    .limit(32)   // calling the limit method
    .toArray(SomeClass[]::new);
Array sekarang dibatasi hingga 32 elemen. Anda bahkan dapat menetapkan nilai objek tertentu untuk setiap elemen array, seperti yang ditunjukkan di bawah ini:
SomeClass[] array = Stream.generate(() -> {
    SomeClass result = new SomeClass();
    result.val = 16;
    return result;
    })
    .limit(32)
    .toArray(SomeClass[]::new);
Kode ini menunjukkan kekuatan ekspresi lambda, namun kodenya tidak rapi atau kompak. Menurut saya, memanggil konstruktor lain untuk menetapkan nilainya akan jauh lebih baik.
SomeClass[] array6 = Stream.generate( () -> new SomeClass(16) )
    .limit(32)
    .toArray(SomeClass[]::new);
Saya suka solusi berbasis ekspresi lambda. Ini ideal ketika Anda perlu memanggil konstruktor tertentu atau bekerja dengan setiap elemen array. Ketika saya membutuhkan sesuatu yang lebih sederhana, saya biasanya menggunakan solusi berbasis obat generik karena lebih sederhana. Namun, Anda dapat melihat sendiri bahwa ekspresi lambda memberikan solusi yang elegan dan fleksibel.

Kesimpulan

Hari ini kita belajar cara bekerja dengan mendeklarasikan dan mengalokasikan array primitif, mengalokasikan array elemen Object , menggunakan refleksi, generik, dan ekspresi lambda di Java.
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION