Haruskah saya menggunakan blok penginisialisasi di Java?

16

Saya baru-baru ini menemukan konstruk Java yang belum pernah saya lihat sebelumnya dan bertanya-tanya apakah saya harus menggunakannya. Tampaknya disebut blok penginisialisasi .

public class Test {
  public Test() { /* first constructor */ }
  public Test(String s) { /* second constructor */ }

  // Non-static initializer block - copied into every constructor:
  {
    doStuff();
  }
}

Blok kode akan disalin ke setiap konstruktor, yaitu jika Anda memiliki beberapa konstruktor, Anda tidak perlu menulis ulang kode.

Namun, saya melihat tiga kelemahan utama menggunakan sintaks ini:

  1. Ini adalah salah satu dari sedikit kasus di Jawa di mana urutan kode Anda penting, karena Anda dapat menentukan beberapa blok kode dan akan dieksekusi dalam urutan yang ditulis. Ini tampaknya berbahaya bagi saya karena hanya mengubah urutan blok kode akan benar-benar mengubah kode.
  2. Saya tidak benar-benar melihat manfaatnya dengan menggunakannya. Dalam kebanyakan kasus, konstruktor akan memanggil satu sama lain dengan beberapa nilai yang telah ditentukan. Bahkan jika ini tidak terjadi, kode hanya dapat dimasukkan ke dalam metode pribadi dan dipanggil dari setiap konstruktor.
  3. Ini mengurangi keterbacaan, karena Anda bisa meletakkan blok di akhir kelas dan konstruktor biasanya di awal kelas. Sangat kontra-intuitif untuk melihat bagian yang sama sekali berbeda dari file kode jika Anda tidak mengharapkan hal itu diperlukan.

Jika pernyataan saya di atas benar, mengapa (dan kapan) konstruksi bahasa ini diperkenalkan? Apakah ada kasus penggunaan yang sah?

Pasang kembali Monica - dirkk
sumber
3
Contoh yang Anda poskan tidak menyertakan apa pun yang tampak seperti blok penginisialisasi.
Simon B
6
@SimonBarker melihat lagi - { doStuff(); }pada tingkat kelas adalah blok penginisialisasi.
amon
@SimonBarker Kode blok yang mengelilingidoStuff()
mengembalikan Monica - dirkk
2
"[S] menyiratkan mengubah urutan blok kode akan benar-benar mengubah kode." Dan apa bedanya dengan mengubah urutan inisialisasi variabel atau setiap baris kode? Jika tidak ada dependensi, maka tidak ada kerusakan yang terjadi, dan jika ada dependensi, maka menempatkan dependensi tidak sesuai adalah sama dengan dependensi misordering untuk setiap baris kode. Hanya karena Java memungkinkan Anda merujuk ke metode dan kelas sebelum didefinisikan tidak berarti bahwa kode dependen jarang di Jawa.
JAB

Jawaban:

20

Ada dua kasus di mana saya menggunakan blok penginisialisasi.

Yang pertama adalah untuk menginisialisasi anggota akhir. Di Jawa, Anda dapat menginisialisasi anggota akhir baik sesuai dengan deklarasi, atau Anda dapat menginisialisasi dalam konstruktor. Dalam suatu metode, dilarang untuk menugaskan anggota akhir.

Ini valid:

final int val = 2;

Ini juga berlaku:

final int val;

MyClass() {
    val = 2;
}

Ini tidak valid:

final int val;

MyClass() {
    init();
}

void init() {
    val = 2;  // cannot assign to 'final' field in a method
}

Jika Anda memiliki banyak konstruktor, dan jika Anda tidak dapat menginisialisasi anggota akhir sebaris (karena logika inisialisasi terlalu kompleks), atau jika konstruktor tidak dapat menyebut diri mereka, maka Anda dapat menyalin / menempelkan kode inisialisasi, atau Anda dapat menggunakan blok penginisialisasi.

final int val;
final int squareVal;

MyClass(int v, String s) {
    this.val = v;
    this.s = s;
}

MyClass(Point p, long id) {
    this.val = p.x;
    this.id = id;
}

{
    squareVal = val * val;
}

Kasus penggunaan lain yang saya miliki untuk blok penginisialisasi adalah untuk membangun struktur data pembantu kecil. Saya mendeklarasikan anggota, dan meletakkan nilai di dalamnya tepat setelah deklarasi di blok penginisialisasi sendiri.

private Map<String, String> days = new HashMap<String, String>();
{
    days.put("mon", "monday");
    days.put("tue", "tuesday");
    days.put("wed", "wednesday");
    days.put("thu", "thursday");
    days.put("fri", "friday");
    days.put("sat", "saturday");
    days.put("sun", "sunday");
}
barjak
sumber
Bukan metode panggilan yang tidak valid. Kode di dalam metode init yang tidak valid. Hanya konstruktor dan blok initalizer yang dapat menetapkan ke variabel anggota akhir, sehingga tugas di init tidak akan dikompilasi.
barjak
Blok kode keempat Anda tidak dikompilasi. Blok initalizer dijalankan sebelum semua konstruktor, karena itu squareVal = val * valakan mengeluh tentang mengakses nilai yang tidak diinisialisasi. Blok inisialisasi tidak mungkin bergantung pada argumen yang diteruskan ke konstruktor. Solusi yang biasa saya lihat untuk masalah semacam itu adalah mendefinisikan konstruktor "basis" tunggal dengan logika kompleks, dan untuk mendefinisikan semua konstruktor lain dalam hal itu. Sebagian besar penggunaan inisialisasi instance, pada kenyataannya, dapat diganti dengan pola itu.
Malnormalulo
11

Secara umum, jangan gunakan blok penginisialisasi non-statis (dan mungkin hindari yang statis juga).

Sintaks yang membingungkan

Melihat pertanyaan ini, ada 3 jawaban, namun Anda membodohi 4 orang dengan sintaks ini. Saya adalah salah satu dari mereka dan saya telah menulis Java selama 16 tahun! Jelas, sintaksnya berpotensi rawan kesalahan! Aku akan menjauh darinya.

Konstruktor Telescoping

Untuk hal-hal yang sangat sederhana, Anda dapat menggunakan konstruktor "telescoping" untuk menghindari kebingungan ini:

public class Test {
    private String something;

    // Default constructor does some things
    public Test() { doStuff(); }

    // Other constructors call the default constructor
    public Test(String s) {
        this(); // Call default constructor
        something = s;
    }
}

Pola Pembangun

Jika Anda perlu melakukanStuff () di akhir setiap konstruktor atau inisialisasi canggih lainnya, mungkin pola pembangun akan menjadi yang terbaik. Josh Bloch mendaftar beberapa alasan mengapa pembangun adalah ide yang bagus. Pembangun membutuhkan sedikit waktu untuk menulis, tetapi ditulis dengan benar, mereka senang digunakan.

public class Test {
    // Value can be final (immutable)
    private final String something;

    // Private constructor.
    private Test(String s) { something = s; }

    // Static method to get a builder
    public static Builder builder() { return new Builder(); }

    // builder class accumulates values until a valid Test object can be created. 
    private static class Builder {
        private String tempSomething;
        public Builder something(String s) {
            tempSomething = s;
            return this;
        }
        // This is our factory method for a Test class.
        public Test build() {
            Test t = new Test(tempSomething);
            // Here we do your extra initialization after the
            // Test class has been created.
            doStuff();
            // Return a valid, potentially immutable Test object.
            return t;
        }
    }
}

// Now you can call:
Test t = Test.builder()
             .setString("Utini!")
             .build();

Initializer Loop Statis

Saya sering menggunakan penginisialisasi statis banyak, tetapi kadang-kadang berlari ke loop di mana 2 kelas tergantung pada blok penginisialisasi statis masing-masing dipanggil sebelum kelas bisa dimuat penuh. Ini menghasilkan "gagal memuat kelas" atau pesan kesalahan yang juga tidak jelas. Saya harus membandingkan file dengan versi kerja terakhir yang diketahui dalam kontrol sumber untuk mencari tahu apa masalahnya. Tidak menyenangkan sama sekali.

Inisialisasi Malas

Mungkin inisialisasi statis baik untuk alasan kinerja ketika mereka bekerja dan tidak terlalu membingungkan. Tetapi secara umum, saya lebih suka inisialisasi malas daripada inisialisasi statis hari ini. Sudah jelas apa yang mereka lakukan, saya belum menemukan bug pemuatan kelas dengan mereka, dan mereka bekerja dalam lebih banyak situasi inisialisasi daripada blok inisialisasi.

Definisi Data

Alih-alih inisialisasi statis untuk membangun struktur data, (bandingkan dengan contoh dalam jawaban lain), saya sekarang menggunakan fungsi pembantu definisi data Paguro yang tidak berubah :

private ImMap<String,String> days =
        map(tup("mon", "monday"),
            tup("tue", "tuesday"),
            tup("wed", "wednesday"),
            tup("thu", "thursday"),
            tup("fri", "friday"),
            tup("sat", "saturday"),
            tup("sun", "sunday"));

Conculsion

Pada awal Java, blok initializer adalah satu-satunya cara untuk melakukan beberapa hal, tetapi sekarang mereka membingungkan, rentan kesalahan, dan dalam banyak kasus telah digantikan oleh alternatif yang lebih baik (dirinci di atas). Sangat menarik untuk mengetahui tentang blok penginisialisasi jika Anda melihatnya dalam kode warisan, atau mereka muncul pada suatu pengujian, tetapi jika saya sedang melakukan tinjauan kode dan saya melihatnya dalam kode baru, saya akan meminta Anda untuk membenarkan mengapa tidak ada alternatif di atas cocok sebelum memberikan kode Anda jempol.

GlenPeterson
sumber
3

Selain inisialisasi variabel instan yang dinyatakan sebagai final(lihat jawaban barjak ), saya juga akan menyebutkan staticblok inisialisasi.

Anda dapat menggunakannya sebagai "kontruktor statis".

Dengan begitu Anda dapat melakukan inisialisasi kompleks pada variabel statis saat pertama kali kelas direferensikan.

Ini adalah contoh yang terinspirasi dari contoh barjak:

public class dayHelper(){
    private static Map<String, String> days = new HashMap<String, String>();
    static {
        days.put("mon", "monday");
        days.put("tue", "tuesday");
        days.put("wed", "wednesday");
        days.put("thu", "thursday");
        days.put("fri", "friday");
        days.put("sat", "saturday");
        days.put("sun", "sunday");
    }
    public static String getLongName(String shortName){
         return days.get(shortName);
    }
}
C.Champagne
sumber
1

Seperti halnya blok penginisialisasi non-statis, fungsi kosongnya adalah untuk bertindak sebagai konstruktor default di kelas anonim. Itu pada dasarnya satu-satunya hak mereka untuk hidup.

Nico
sumber
0

Saya sepenuhnya setuju dengan pernyataan 1, 2, 3. Saya juga tidak pernah menggunakan inisialisasi blok karena alasan ini dan saya tidak tahu mengapa itu ada di Jawa.

Namun, saya dipaksa untuk menggunakan initializer blok statis dalam satu kasus: ketika saya harus instantiate bidang statis yang konstruktornya dapat melempar pengecualian diperiksa.

private static final JAXBContext context = JAXBContext.newInstance(Foo.class); //doesn't compile

Tetapi yang harus Anda lakukan:

private static JAXBContext context;
static {
    try
    {
        context = JAXBContext.newInstance(Foo.class);
    }
    catch (JAXBException e)
    {
        //seriously...
    }
}

Saya menemukan idiom ini sangat jelek (itu juga mencegah Anda untuk menandai contextsebagai final) tetapi ini adalah satu-satunya cara yang didukung oleh Java untuk menginisialisasi bidang tersebut.

Tutul
sumber
Saya pikir jika Anda mengatur context = null;di blok tangkap Anda, bahwa Anda mungkin dapat menyatakan konteks sebagai final.
GlenPeterson
@ GlenPeterson Saya mencoba tetapi tidak dapat dikompilasi:The final field context may already have been assigned
Melihat
Ups! Saya yakin Anda dapat membuat konteks Anda final jika Anda memperkenalkan variabel lokal di dalam blok statis:static { JAXBContext tempCtx = null; try { tempCtx = JAXBContext.newInstance(Foo.class); } catch (JAXBException ignored) { ; } context = tempCtx; }
GlenPeterson