Apa inisialisasi Double Brace di Jawa?

307

Apa itu sintaks inisialisasi Double Brace ( {{ ... }}) di Java?

sgokhales
sumber
2
Lihat juga stackoverflow.com/q/924285/45935
Jim Ferrans
10
Inisialisasi Double Brace adalah fitur yang sangat berbahaya dan harus digunakan dengan bijaksana. Itu mungkin melanggar sama dengan kontrak dan menyebabkan kebocoran memori yang rumit. Artikel ini menjelaskan detailnya.
Andrii Polunin

Jawaban:

303

Inisialisasi penguat ganda menciptakan kelas anonim yang berasal dari kelas yang ditentukan ( kurung luar ), dan menyediakan blok penginisialisasi dalam kelas tersebut ( kurung dalam ). misalnya

new ArrayList<Integer>() {{
   add(1);
   add(2);
}};

Perhatikan bahwa efek menggunakan inisialisasi penjepit ganda ini adalah Anda membuat kelas dalam anonim. Kelas yang dibuat memiliki thispointer implisit ke kelas luar sekitarnya. Meskipun biasanya tidak menjadi masalah, itu dapat menyebabkan kesedihan dalam beberapa keadaan misalnya ketika membuat cerita bersambung atau mengumpulkan sampah, dan ada baiknya menyadari hal ini.

Brian Agnew
sumber
11
Terima kasih telah memperjelas arti kawat gigi bagian dalam dan luar. Saya bertanya-tanya mengapa tiba-tiba ada dua kawat gigi yang diizinkan dengan makna khusus, padahal sebenarnya itu adalah konstruksi java normal yang hanya muncul sebagai beberapa trik baru yang ajaib. Hal-hal seperti itu membuat saya mempertanyakan sintaks Java. Jika Anda bukan seorang ahli, akan sangat sulit untuk membaca dan menulis.
jackthehipster
4
"Sintaks ajaib" seperti ini ada dalam banyak bahasa, misalnya hampir semua bahasa mirip-C mendukung sintaks "go to 0" dari "x -> 0" in untuk loop yang hanya "x--> 0" dengan aneh penempatan ruang.
Joachim Sauer
17
Kita dapat menyimpulkan bahwa "inisialisasi penjepit ganda" tidak ada dengan sendirinya, itu hanya kombinasi dari menciptakan kelas anonim dan blok penginisialisasi , yang, sekali digabungkan, terlihat seperti konstruksi sintaksis, tetapi pada kenyataannya, itu bukan t.
MC Emperor
Terima kasih! Gson mengembalikan nol ketika kita membuat serial sesuatu dengan inisialisasi penjepit ganda karena penggunaan kelas dalam anonim.
Pradeep AJ- Msft MVP
295

Setiap kali seseorang menggunakan inisialisasi penjepit ganda, anak kucing terbunuh.

Terlepas dari sintaks yang agak tidak biasa dan tidak benar-benar idiomatis (rasa bisa diperdebatkan, tentu saja), Anda tidak perlu menciptakan dua masalah signifikan dalam aplikasi Anda, yang baru-baru ini saya blogging tentang lebih detail di sini .

1. Anda membuat terlalu banyak kelas anonim

Setiap kali Anda menggunakan inisialisasi brace ganda, kelas baru dibuat. Misalnya contoh ini:

Map source = new HashMap(){{
    put("firstName", "John");
    put("lastName", "Smith");
    put("organizations", new HashMap(){{
        put("0", new HashMap(){{
            put("id", "1234");
        }});
        put("abc", new HashMap(){{
            put("id", "5678");
        }});
    }});
}};

... akan menghasilkan kelas-kelas ini:

Test$1$1$1.class
Test$1$1$2.class
Test$1$1.class
Test$1.class
Test.class

Itu sedikit overhead untuk classloader Anda - gratis! Tentu saja tidak akan memakan banyak waktu inisialisasi jika Anda melakukannya sekali. Tetapi jika Anda melakukan ini 20'000 kali di seluruh aplikasi perusahaan Anda ... semua itu menumpuk memori hanya untuk sedikit "sintaks gula"?

2. Anda berpotensi membuat kebocoran memori!

Jika Anda mengambil kode di atas dan mengembalikan peta itu dari suatu metode, penelepon dari metode itu mungkin secara tidak curiga berpegangan pada sumber daya yang sangat berat yang tidak dapat dikumpulkan dari sampah. Perhatikan contoh berikut:

public class ReallyHeavyObject {

    // Just to illustrate...
    private int[] tonsOfValues;
    private Resource[] tonsOfResources;

    // This method almost does nothing
    public Map quickHarmlessMethod() {
        Map source = new HashMap(){{
            put("firstName", "John");
            put("lastName", "Smith");
            put("organizations", new HashMap(){{
                put("0", new HashMap(){{
                    put("id", "1234");
                }});
                put("abc", new HashMap(){{
                    put("id", "5678");
                }});
            }});
        }};

        return source;
    }
}

Yang dikembalikan Mapsekarang akan berisi referensi ke instance terlampir ReallyHeavyObject. Anda mungkin tidak ingin mengambil risiko itu:

Memori Kebocoran Di Sini

Gambar dari http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/

3. Anda dapat berpura-pura bahwa Jawa memiliki peta literal

Untuk menjawab pertanyaan Anda yang sebenarnya, orang-orang telah menggunakan sintaks ini untuk berpura-pura bahwa Java memiliki sesuatu seperti peta literal, mirip dengan liter array yang ada:

String[] array = { "John", "Doe" };
Map map = new HashMap() {{ put("John", "Doe"); }};

Beberapa orang mungkin merasa ini merangsang secara sintaksis.

Lukas Eder
sumber
11
"Anda menciptakan terlalu banyak kelas anonim" - melihat bagaimana (katakanlah) Scala menciptakan kelas anonim, saya tidak terlalu yakin bahwa ini adalah masalah besar
Brian Agnew
2
Bukankah itu tetap merupakan cara yang sah dan bagus untuk mendeklarasikan peta statis? Jika HashMap diinisialisasi {{...}}dan dinyatakan sebagai staticbidang, seharusnya tidak ada kebocoran memori yang mungkin, hanya satu kelas anonim dan tidak ada referensi instance terlampir, bukan?
lorenzo-s
8
@ Lorenzo-s: Ya, 2) dan 3) tidak berlaku, hanya 1). Untungnya, dengan Java 9, akhirnya ada Map.of()untuk tujuan itu, jadi itu akan menjadi solusi yang lebih baik
Lukas Eder
3
Mungkin perlu dicatat bahwa peta dalam juga memiliki referensi ke peta luar dan karenanya, secara tidak langsung ReallyHeavyObject. Juga, kelas dalam anonim menangkap semua variabel lokal yang digunakan dalam tubuh kelas, jadi jika Anda menggunakan tidak hanya konstanta untuk menginisialisasi koleksi atau peta dengan pola ini, instance kelas dalam akan menangkap semuanya dan terus referensi mereka bahkan ketika sebenarnya dihapus dari koleksi atau peta. Jadi jika demikian, contoh-contoh ini tidak hanya membutuhkan dua kali memori yang diperlukan untuk referensi, tetapi memiliki kebocoran memori lain dalam hal itu.
Holger
41
  • Penjepit pertama menciptakan Kelas Batin Anonim baru.
  • Set kedua brace menciptakan inisialisasi instance seperti blok statis di Kelas.

Sebagai contoh:

   public class TestHashMap {
    public static void main(String[] args) {
        HashMap<String,String> map = new HashMap<String,String>(){
        {
            put("1", "ONE");
        }{
            put("2", "TWO");
        }{
            put("3", "THREE");
        }
        };
        Set<String> keySet = map.keySet();
        for (String string : keySet) {
            System.out.println(string+" ->"+map.get(string));
        }
    }

}

Bagaimana itu bekerja

Penjepit pertama menciptakan Kelas Batin Anonim baru. Kelas-kelas dalam ini mampu mengakses perilaku kelas induknya. Jadi, dalam kasus kami, kami sebenarnya membuat subkelas dari kelas HashSet, sehingga kelas dalam ini mampu menggunakan metode put ().

Dan set kedua kawat gigi tidak lain adalah inisialisasi instance. Jika Anda mengingatkan konsep inti Java maka Anda dapat dengan mudah mengaitkan blok initializer contoh dengan initializers statis karena kurung mirip seperti struct. Satu-satunya perbedaan adalah bahwa penginisialisasi statis ditambahkan dengan kata kunci statis, dan dijalankan hanya sekali; tidak peduli berapa banyak objek yang Anda buat.

lebih

Premraj
sumber
24

Untuk aplikasi menyenangkan inisialisasi penjepit ganda, lihat di sini Array Dwemthy di Jawa .

Kutipan

private static class IndustrialRaverMonkey
  extends Creature.Base {{
    life = 46;
    strength = 35;
    charisma = 91;
    weapon = 2;
  }}

private static class DwarvenAngel
  extends Creature.Base {{
    life = 540;
    strength = 6;
    charisma = 144;
    weapon = 50;
  }}

Dan sekarang, bersiaplah untuk BattleOfGrottoOfSausageSmellsdan ... daging tebal!

akuhn
sumber
16

Saya pikir penting untuk menekankan bahwa tidak ada yang namanya "inisialisasi Brace Ganda" di Jawa . Situs web Oracle tidak memiliki istilah ini. Dalam contoh ini ada dua fitur yang digunakan bersama: kelas anonim dan blok penginisialisasi. Sepertinya blok penginisialisasi lama telah dilupakan oleh pengembang dan menyebabkan beberapa kebingungan dalam topik ini. Kutipan dari Oracle docs :

Blok inisialisasi untuk variabel contoh terlihat seperti blok inisialisasi statis, tetapi tanpa kata kunci statis:

{
    // whatever code is needed for initialization goes here
}
Alex T
sumber
11

1- Tidak ada yang namanya kawat gigi ganda:
Saya ingin menunjukkan bahwa tidak ada yang namanya inisialisasi kawat gigi ganda . Hanya ada blok inisialisasi tradisional satu penyangga normal. Blok kawat gigi kedua tidak ada hubungannya dengan inisialisasi. Jawaban mengatakan bahwa kedua kawat gigi menginisialisasi sesuatu, tetapi tidak seperti itu.

2- Ini bukan hanya tentang kelas anonim tetapi semua kelas:
Hampir semua jawaban berbicara bahwa itu adalah hal yang digunakan saat membuat kelas batin anonim. Saya pikir orang-orang yang membaca jawaban-jawaban itu akan mendapatkan kesan bahwa ini hanya digunakan ketika membuat kelas batin anonim. Tetapi digunakan di semua kelas. Membaca jawaban-jawaban itu sepertinya adalah beberapa fitur khusus baru yang didedikasikan untuk kelas anonim dan saya pikir itu menyesatkan.

3 - Tujuannya adalah tentang menempatkan braket satu sama lain, bukan konsep baru:
Lebih jauh, pertanyaan ini berbicara tentang situasi ketika braket pembuka kedua tepat setelah braket pembuka pertama. Ketika digunakan di kelas normal biasanya ada beberapa kode antara dua kawat gigi, tetapi sama sekali hal yang sama. Jadi itu masalah menempatkan kurung. Jadi saya pikir kita tidak boleh mengatakan bahwa ini adalah hal baru yang menarik, karena ini adalah hal yang kita semua tahu, tetapi hanya ditulis dengan beberapa kode di antara kurung. Kita seharusnya tidak membuat konsep baru yang disebut "inisialisasi penjepit ganda".

4- Membuat kelas anonim bersarang tidak ada hubungannya dengan dua kawat gigi:
Saya tidak setuju dengan argumen bahwa Anda membuat terlalu banyak kelas anonim. Anda tidak membuatnya karena blok inisialisasi, tetapi hanya karena Anda membuatnya. Mereka akan dibuat bahkan jika Anda tidak menggunakan inisialisasi dua kawat gigi sehingga masalah-masalah itu akan terjadi bahkan tanpa inisialisasi ... Inisialisasi bukanlah faktor yang menciptakan objek yang diinisialisasi.

Selain itu kita tidak boleh berbicara tentang masalah yang dibuat dengan menggunakan hal yang tidak ada ini "inisialisasi penjepit ganda" atau bahkan dengan inisialisasi satu braket normal, karena masalah yang dijelaskan ada hanya karena membuat kelas anonim sehingga tidak ada hubungannya dengan pertanyaan asli. Tetapi semua jawaban dengan memberi kesan kepada pembaca bahwa itu bukan kesalahan menciptakan kelas anonim, tetapi hal jahat (tidak ada) ini disebut "inisialisasi penjepit ganda".

ctomek
sumber
9

Untuk menghindari semua efek negatif dari inisialisasi penjepit ganda, seperti:

  1. Kompatibilitas "sama dengan" rusak.
  2. Tidak ada pemeriksaan yang dilakukan, saat menggunakan penugasan langsung.
  3. Kemungkinan memori bocor.

lakukan hal-hal selanjutnya:

  1. Buat kelas "Builder" terpisah terutama untuk inisialisasi penjepit ganda.
  2. Deklarasikan bidang dengan nilai default.
  3. Masukkan metode pembuatan objek di kelas itu.

Contoh:

public class MyClass {
    public static class Builder {
        public int    first  = -1        ;
        public double second = Double.NaN;
        public String third  = null      ;

        public MyClass create() {
            return new MyClass(first, second, third);
        }
    }

    protected final int    first ;
    protected final double second;
    protected final String third ;

    protected MyClass(
        int    first ,
        double second,
        String third
    ) {
        this.first = first ;
        this.second= second;
        this.third = third ;
    }

    public int    first () { return first ; }
    public double second() { return second; }
    public String third () { return third ; }
}

Pemakaian:

MyClass my = new MyClass.Builder(){{ first = 1; third = "3"; }}.create();

Keuntungan:

  1. Cukup digunakan.
  2. Jangan merusak kompatibilitas "sama dengan".
  3. Anda dapat melakukan pemeriksaan dalam metode pembuatan.
  4. Tidak ada kebocoran memori.

Kekurangan:

  • Tidak ada

Dan, sebagai hasilnya, kami memiliki pola java builder paling sederhana.

Lihat semua sampel di github: java-sf-builder-simple-example

Boris Podchezertsev
sumber
4

Ini - di antara kegunaan lain - jalan pintas untuk menginisialisasi koleksi. Belajarlah lagi ...

miku
sumber
2
Ya, itu satu aplikasi untuk itu, tetapi tidak berarti satu-satunya.
skaffman
4

Apakah maksudmu seperti ini?

List<String> blah = new ArrayList<String>(){{add("asdfa");add("bbb");}};

ini merupakan inisialisasi daftar array dalam waktu pembuatan (retas)

dhblah
sumber
4

Anda bisa meletakkan beberapa pernyataan Java sebagai loop untuk menginisialisasi koleksi:

List<Character> characters = new ArrayList<Character>() {
    {
        for (char c = 'A'; c <= 'E'; c++) add(c);
    }
};

Random rnd = new Random();

List<Integer> integers = new ArrayList<Integer>() {
    {
         while (size() < 10) add(rnd.nextInt(1_000_000));
    }
};

Tapi kasus ini memengaruhi kinerja, periksa diskusi ini

Anton Dozortsev
sumber
4

Seperti yang ditunjukkan oleh @Lukas Eder inisialisasi kawat gigi ganda koleksi harus dihindari.

Itu menciptakan kelas batin anonim, dan karena semua kelas internal tetap referensi ke instance induk, ia dapat - dan 99% kemungkinan akan - mencegah pengumpulan sampah jika objek pengumpulan ini dirujuk oleh lebih banyak objek daripada hanya yang mendeklarasikan.

Java 9 telah memperkenalkan metode kenyamanan List.of, Set.ofdan Map.of, yang harus digunakan sebagai gantinya. Mereka lebih cepat dan lebih efisien daripada penginisialisasi double-brace.

Mikhail Kholodkov
sumber
0

Penjepit pertama membuat Kelas Anonim baru dan set penjepit kedua menciptakan inisialisasi instance seperti blok statis.

Seperti yang telah ditunjukkan orang lain, itu tidak aman untuk digunakan.

Namun, Anda selalu dapat menggunakan alternatif ini untuk menginisialisasi koleksi.

  • Java 8
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
  • Jawa 9
List<String> list = List.of("A", "B", "C");
Ankit Sharma
sumber
-1

Ini akan tampak sama dengan kata kunci dengan begitu populer di flash dan vbscript. Ini adalah metode untuk mengubah apa yang thisada dan tidak lebih.

Chuck Vose
sumber
Tidak juga. Itu seperti mengatakan menciptakan kelas baru adalah metode untuk mengubah apathis ada. Sintaks hanya membuat kelas anonim (jadi referensi apa pun thisakan merujuk ke objek dari kelas anonim baru), dan kemudian menggunakan blok {...}penginisialisasi untuk menginisialisasi instance yang baru dibuat.
Grinch