JavaRush /Blog Java /Random-MS /Rehat kopi #130. Bagaimana untuk bekerja dengan tatasusun...

Rehat kopi #130. Bagaimana untuk bekerja dengan tatasusunan Java dengan betul - petua dari Oracle

Diterbitkan dalam kumpulan
Sumber: Oracle Bekerja dengan tatasusunan boleh merangkumi ungkapan refleksi, generik dan lambda. Saya baru-baru ini bercakap dengan rakan sekerja yang berkembang dalam C. Perbualan bertukar kepada tatasusunan dan cara ia berfungsi di Jawa berbanding dengan C. Saya mendapati ini agak pelik, memandangkan Java dianggap sebagai bahasa seperti C. Mereka sebenarnya mempunyai banyak persamaan, tetapi terdapat juga perbezaan. Mari kita mulakan dengan mudah. Rehat kopi #130.  Bagaimana untuk bekerja dengan betul dengan tatasusunan Java - petua dari Oracle - 1

Pengisytiharan Array

Jika anda mengikuti tutorial Java, anda akan melihat bahawa terdapat dua cara untuk mengisytiharkan tatasusunan. Yang pertama adalah mudah:
int[] array; // a Java array declaration
Anda boleh melihat bagaimana ia berbeza daripada C, di mana sintaksnya:
int array[]; // a C array declaration
Mari kembali semula ke Jawa. Selepas mengisytiharkan tatasusunan, anda perlu memperuntukkannya:
array = new int[10]; // Java array allocation
Adakah mungkin untuk mengisytiharkan dan memulakan tatasusunan sekaligus? Sebenarnya tidak:
int[10] array; // NOPE, ERROR!
Walau bagaimanapun, anda boleh mengisytiharkan dan memulakan tatasusunan dengan segera jika anda sudah mengetahui nilainya:
int[] array = { 0, 1, 1, 2, 3, 5, 8 };
Bagaimana jika anda tidak tahu maksudnya? Berikut ialah kod yang paling kerap anda lihat untuk mengisytiharkan, memperuntukkan dan menggunakan tatasusunan 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;
...
Ambil perhatian bahawa saya menetapkan tatasusunan int , yang merupakan tatasusunan jenis data primitif Java . Mari lihat apa yang berlaku jika anda mencuba proses yang sama dengan pelbagai objek Java dan bukannya 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 kod di atas, kita akan mendapat pengecualian serta-merta selepas cuba menggunakan elemen pertama tatasusunan. kenapa? Walaupun tatasusunan diperuntukkan, setiap segmen tatasusunan mengandungi rujukan objek kosong. Jika anda memasukkan kod ini ke dalam IDE anda, ia akan mengisi .val secara automatik untuk anda, jadi ralat mungkin mengelirukan. Untuk membetulkan pepijat, ikut 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;
Tetapi ia tidak elegan. Saya tertanya-tanya mengapa saya tidak boleh dengan mudah memperuntukkan tatasusunan dan objek dalam tatasusunan dengan kurang kod, mungkin juga semua pada satu baris. Untuk mencari jawapannya, saya menjalankan beberapa eksperimen.

Mencari nirvana antara tatasusunan Java

Matlamat kami adalah untuk membuat kod secara elegan. Mengikuti peraturan "kod bersih", saya memutuskan untuk mencipta kod boleh guna semula untuk membersihkan corak peruntukan tatasusunan. Inilah percubaan 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 kod bertanda "lihat ini" kelihatan betul-betul seperti yang saya mahukan, terima kasih kepada pelaksanaan toArray . Pendekatan ini menggunakan refleksi untuk mencari pembina lalai untuk kelas yang disediakan dan kemudian memanggil pembina itu untuk membuat instantiate objek kelas itu. Proses memanggil pembina sekali untuk setiap elemen tatasusunan. Hebat! Cuma sayangnya ia tidak berfungsi. Kod disusun dengan baik, tetapi membuang ralat ClassCastException apabila dijalankan. Untuk menggunakan kod ini, anda perlu mencipta tatasusunan elemen Objek , dan kemudian menghantar setiap elemen tatasusunan ke kelas SomeClass seperti ini:
Object[] objects = MyArray.toArray(SomeClass.class, 32);
SomeClass scObj = (SomeClass)objects[0];
...
Ini tidak elegan! Selepas lebih banyak percubaan, saya membangunkan beberapa penyelesaian menggunakan ungkapan refleksi, generik dan lambda.

Penyelesaian 1: Gunakan refleksi

Di sini kami menggunakan kelas java.lang.reflect.Array untuk membuat instantiate tatasusunan kelas yang anda tentukan dan bukannya menggunakan kelas asas java.lang.Object . Ini pada asasnya ialah perubahan kod 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 boleh menggunakan pendekatan ini untuk mendapatkan tatasusunan kelas yang dikehendaki dan kemudian bekerja dengannya seperti ini:
SomeClass[] array1 = (SomeClass[])MyArray.toArray(SomeClass.class, 32);
Walaupun ini bukan perubahan yang diperlukan, baris kedua telah ditukar untuk menggunakan kelas pantulan Array untuk menetapkan kandungan setiap elemen tatasusunan. Ini luar biasa! Tetapi ada satu lagi butiran yang nampaknya tidak betul: pelakon ke SomeClass[] tidak kelihatan sangat bagus. Nasib baik, ada penyelesaian dengan generik.

Penyelesaian 2: Gunakan generik

Rangka kerja Koleksi menggunakan generik untuk mengikat jenis dan menghapuskan hantaran kepada mereka dalam kebanyakan operasinya. Generik juga boleh digunakan di sini. Mari kita ambil contoh java.util.List .
List list = new ArrayList();
list.add( new SomeClass() );
SomeClass sc = list.get(0); // Error, needs a cast unless...
Baris ketiga dalam coretan di atas akan menimbulkan ralat melainkan anda mengemas kini baris pertama seperti ini:
List<SomeClass> = new ArrayList();
Anda boleh mencapai hasil yang sama dengan menggunakan generik dalam kelas MyArray . Inilah versi baharu:
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();
Ia kelihatan baik. Dengan menggunakan generik dan memasukkan jenis sasaran dalam pengisytiharan, jenis itu boleh disimpulkan dalam operasi lain. Selain itu, kod ini boleh dikurangkan kepada satu baris dengan melakukan ini:
SomeClass[] array = new MyArray<SomeClass>(SomeClass.class, 32).toArray();
Misi tercapai, kan? Nah, tidak cukup. Ini baik jika anda tidak peduli pembina kelas mana yang anda panggil, tetapi jika anda ingin memanggil pembina tertentu, maka penyelesaian ini tidak berfungsi. Anda boleh terus menggunakan refleksi untuk menyelesaikan masalah ini, tetapi kemudian kod itu akan menjadi kompleks. Nasib baik, terdapat ungkapan lambda yang menawarkan penyelesaian lain.

Penyelesaian 3: Gunakan ungkapan lambda

Saya akui, saya tidak begitu teruja dengan ungkapan lambda sebelum ini, tetapi saya telah belajar untuk menghargainya. Khususnya, saya menyukai antara muka java.util.stream.Stream , yang mengendalikan koleksi objek. Strim membantu saya mencapai nirvana tatasusunan Java. Inilah percubaan pertama saya menggunakan lambdas:
SomeClass[] array =
    Stream.generate(() -> new SomeClass())
    .toArray(SomeClass[]::new);
Saya telah memecahkan kod ini kepada tiga baris untuk bacaan yang lebih mudah. Anda boleh melihat bahawa ia menandakan semua kotak: ia mudah dan elegan, mencipta pelbagai contoh objek yang dihuni dan membolehkan anda memanggil pembina tertentu. Beri perhatian kepada parameter kaedah toArray : SomeClass[]::new . Ini ialah fungsi penjana yang digunakan untuk memperuntukkan tatasusunan jenis yang ditentukan. Walau bagaimanapun, seperti sedia ada, kod ini mempunyai masalah kecil: ia mencipta pelbagai saiz tak terhingga. Ini tidak begitu optimum. Tetapi masalah itu boleh diselesaikan dengan memanggil kaedah had :
SomeClass[] array =
    Stream.generate(() -> new SomeClass())
    .limit(32)   // calling the limit method
    .toArray(SomeClass[]::new);
Tatasusunan kini terhad kepada 32 elemen. Anda juga boleh menetapkan nilai objek tertentu untuk setiap elemen tatasusunan, seperti yang ditunjukkan di bawah:
SomeClass[] array = Stream.generate(() -> {
    SomeClass result = new SomeClass();
    result.val = 16;
    return result;
    })
    .limit(32)
    .toArray(SomeClass[]::new);
Kod ini menunjukkan kuasa ungkapan lambda, tetapi kod itu tidak kemas atau padat. Pada pendapat saya, memanggil pembina lain untuk menetapkan nilai adalah lebih baik.
SomeClass[] array6 = Stream.generate( () -> new SomeClass(16) )
    .limit(32)
    .toArray(SomeClass[]::new);
Saya suka penyelesaian berasaskan ungkapan lambda. Ia sesuai apabila anda perlu memanggil pembina tertentu atau bekerja dengan setiap elemen tatasusunan. Apabila saya memerlukan sesuatu yang lebih mudah, saya biasanya menggunakan penyelesaian berasaskan generik kerana ia lebih mudah. Walau bagaimanapun, anda boleh melihat sendiri bahawa ungkapan lambda menyediakan penyelesaian yang elegan dan fleksibel.

Kesimpulan

Hari ini kita belajar cara bekerja dengan mengisytiharkan dan memperuntukkan tatasusunan primitif, memperuntukkan tatasusunan elemen Objek , menggunakan ungkapan refleksi, generik dan lambda dalam Java.
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION