Що таке ініціалізація подвійної дужки на Java?


306

Що таке синтаксис ініціалізації Double Brace ( {{ ... }}) у Java?



2
Дивіться також stackoverflow.com/q/924285/45935
Джим Ferrans

10
Подвійна ініціалізація Brace є дуже небезпечною особливістю, і її слід використовувати розумно. Це може розірвати рівний контракт і створити складні витоки пам'яті. У цій статті описані деталі.
Андрій Полунін

Відповіді:


302

Подвійна дужка ініціалізації створює анонімний клас, отриманий із зазначеного класу ( зовнішні дужки), та забезпечує блок ініціалізатора всередині цього класу ( внутрішні дужки). напр

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

Зауважте, що ефект використання цієї подвійної дужки ініціалізації полягає в тому, що ви створюєте анонімні внутрішні класи. Створений клас має неявний thisвказівник на навколишній зовнішній клас. Хоча це зазвичай не є проблемою, це може викликати горе за деяких обставин, наприклад, при серіалізації чи збиранні сміття, і про це варто знати.


11
Дякуємо за уточнення значення внутрішніх та зовнішніх брекетів. Я замислювався, чому раптом є два дужки, дозволені з особливим значенням, коли вони насправді є нормальними конструкціями java, які виглядають лише як якась нова магія. Такі речі змушують мене сумніватися у синтаксисі Java. Якщо ви вже не експерт, читати та писати це може бути дуже складно.
Джектіхістер

4
"Чарівний синтаксис", подібний до цього, існує у багатьох мовах, наприклад, майже всі мови, подібні до С, підтримують синтаксис "x -> 0" у для циклів, що є просто "x--> 0" із дивними розміщення місця.
Йоахім Зауер

17
Ми можемо просто зробити висновок, що "подвійна дужка ініціалізація" не існує сама по собі, це лише комбінація створення анонімного класу та ініціалізаційного блоку , який, поєднавшись, виглядає як синтаксична конструкція, але насправді це не так " т.
MC імператор

Дякую! Gson повертає null, коли ми серіалізуємо щось із подвійною ініціалізацією дужок через анонімне використання внутрішнього класу.
Pradeep AJ- Msft MVP

295

Кожного разу, коли хтось використовує подвійну брекет-ініціалізацію, кошеня вбивається.

Окрім того, що синтаксис є досить незвичним і не дуже ідіоматичним (звичайно, смак є дискусійним), ви зайво створюєте дві суттєві проблеми у вашій програмі, про які я нещодавно детальніше розповів тут .

1. Ви створюєте занадто багато анонімних класів

Кожен раз, коли ви використовуєте подвійну дужку ініціалізації, робиться новий клас. Наприклад, такий приклад:

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

... буде виробляти наступні класи:

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

Це зовсім небагато накладних витрат для вашого класного навантажувача - ні за що! Звичайно, це не займе багато часу для ініціалізації, якщо ви зробите це один раз. Але якщо ви зробите це 20 тис. Разів протягом свого корпоративного додатку ... вся ця купа пам’яті лише за трохи «синтаксичного цукру»?

2. Ви потенційно створюєте витік пам'яті!

Якщо ви берете вищевказаний код і повертаєте цю карту з методу, абоненти цього методу можуть нічого не підозрювати на дуже важких ресурсах, які неможливо зібрати сміття. Розглянемо наступний приклад:

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

Тепер повернуті Mapміститимуть посилання на екземпляр, що додається ReallyHeavyObject. Ви, мабуть, не хочете ризикувати цим:

Тут витоку пам'яті

Зображення з http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/

3. Ви можете зробити вигляд, що у Java є літеральна карта

Щоб відповісти на ваше актуальне запитання, люди використовують цей синтаксис, роблячи вигляд, що у Java є щось на зразок літеральних карт, подібних до існуючих літералів масиву:

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

Деякі люди можуть вважати це синтаксично стимулюючим.


11
"Ви створюєте занадто багато анонімних класів" - дивлячись на те, як (скажімо) Скала створює анонімні класи, я не дуже впевнений, що це основна проблема
Брайан Агнев,

2
Хіба це не залишається дійсним і приємним способом оголошення статичних карт? Якщо HashMap ініціалізується з полем {{...}}і оголошується як staticполе, не повинно бути ніякого можливого витоку пам'яті, лише один анонімний клас і відсутні вкладені посилання екземплярів, правда?
lorenzo-s

8
@ lorenzo-s: Так, 2) і 3) тоді не застосовуються, лише 1). На щастя, з Java 9, нарешті, Map.of()для цього є, тож це буде кращим рішенням
Лукас Едер

3
Можливо, варто відзначити, що внутрішні карти також мають посилання на зовнішні карти, а отже, опосередковано і на ReallyHeavyObject. Крім того, анонімні внутрішні класи захоплюють усі локальні змінні, що використовуються в тілі класу, тому якщо ви використовуєте не тільки константи для ініціалізації колекцій чи карт із цим шаблоном, то екземпляри внутрішнього класу захоплюють усі вони та продовжуватимуть посилатися на них навіть тоді, коли їх фактично видалено колекцію чи карту. Отже, цим випадкам не тільки потрібно вдвічі більше необхідної пам’яті для посилань, але й ще одна витік пам’яті.
Холгер

41
  • Перша дужка створює новий Anonymous Inner Class.
  • Другий набір дужок створює ініціалізатори екземплярів на зразок статичного блоку в Class.

Наприклад:

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

}

Як це працює

Перша дужка створює новий Anonymous Inner Class. Ці внутрішні класи здатні отримати доступ до поведінки свого батьківського класу. Отже, у нашому випадку ми фактично створюємо підклас класу HashSet, тому цей внутрішній клас здатний використовувати метод put ().

І другий набір дужок - це не що інше, як ініціалізатори екземплярів. Якщо ви нагадуєте основні поняття java, то ви можете легко асоціювати блоки ініціалізаторів екземплярів зі статичними ініціалізаторами завдяки подібній дузі, як структура. Різниця полягає лише в тому, що статичний ініціалізатор додається до статичного ключового слова і запускається лише один раз; незалежно від того, скільки об’єктів ви створюєте.

більше


24

Для веселого застосування подвійної дужки ініціалізації дивіться тут масив Дьютіті на Java .

Уривок

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

А тепер будьте готові до BattleOfGrottoOfSausageSmells... і грубого бекону!


16

Я думаю, що важливо підкреслити, що у Java немає такого поняття, як "подвійна ініціалізація Brace" . Веб-сайт Oracle не має цього терміна. У цьому прикладі є дві функції, які використовуються разом: анонімний клас та блок ініціалізатора. Схоже, старий блок ініціалізатора забули розробники і викликають певну плутанину в цій темі. Цитування з документів Oracle :

Блоки ініціалізатора, наприклад, змінні виглядають як блоки статичного ініціалізатора, але без статичного ключового слова:

{
    // whatever code is needed for initialization goes here
}

11

1- Немає такого поняття, як подвійні дужки:
я хотів би зазначити, що немає подвійної ініціалізації дужок . Існує лише звичайний традиційний блок ініціалізації брекетів. Другий брекет-блок не має нічого спільного з ініціалізацією. Відповіді кажуть, що ці два дужки щось ініціалізують, але це не так.

2- Не йдеться лише про анонімні класи, але про всі класи:
майже всі відповіді говорять про те, що це річ, яка використовується при створенні анонімних внутрішніх класів. Я думаю, що у людей, які читають ці відповіді, складеться враження, що це використовується лише під час створення анонімних внутрішніх класів. Але він використовується у всіх класах. Читання цих відповідей виглядає так - це абсолютно нова особливість, присвячена анонімним класам, і я думаю, що це вводить в оману.

3- Мета полягає лише у розміщенні дужок один за одним, а не новою концепцією:
далі, це питання говорить про ситуацію, коли друга дужка, що відкривається, знаходиться відразу після першого відкриття дужки. При використанні в звичайному класі зазвичай є код між двома дужками, але це абсолютно одне і те ж. Тож справа в розміщенні дужок. Тому я думаю, що ми не повинні говорити, що це якась нова захоплююча річ, адже це та річ, яку ми всі знаємо, а лише написана з деяким кодом між дужками. Ми не повинні створювати нову концепцію, яка називається "подвійною ініціалізацією дужок".

4- Створення вкладених анонімних класів не має нічого спільного з двома дужками:
я не згоден з аргументом того, що ви створюєте занадто багато анонімних класів. Ви створюєте їх не через ініціалізаційний блок, а просто тому, що ви їх створюєте. Вони будуть створені, навіть якщо б ви не використовували дві ініціалізації брекетів, щоб ці проблеми виникали навіть без ініціалізації ... Ініціалізація не є фактором, який створює ініціалізовані об'єкти.

Крім того, ми не повинні говорити про проблему, створену за допомогою цієї неіснуючої речі "подвійної ініціалізації дужок" або навіть звичайної ініціалізації однієї дужки, оскільки описані проблеми існують лише через створення анонімного класу, тому це не має нічого спільного з оригінальним запитанням. Але всі відповіді створюють у читачів враження, що виною не є створення анонімних класів, а ця зла (неіснуюча) річ, яка називається "подвійною ініціалізацією дужок".


9

Щоб уникнути всіх негативних ефектів подвійної дужки ініціалізації, таких як:

  1. Порушена сумісність "дорівнює".
  2. Жодні перевірки не виконуються при використанні прямих призначень.
  3. Можливі витоки пам'яті.

робити наступні дії:

  1. Складіть окремий клас "Builder", особливо для подвійної ініціалізації дужок.
  2. Оголосити поля зі значеннями за замовчуванням.
  3. Помістіть метод створення об’єктів у цей клас.

Приклад:

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

Використання:

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

Переваги:

  1. Просто у використанні.
  2. Не порушуйте сумісність "дорівнює".
  3. Ви можете виконувати перевірки в методі створення.
  4. Ніяких витоків пам'яті.

Недоліки:

  • Жоден.

І, як наслідок, у нас найпростіший шаблон для побудови Java.

Перегляньте всі зразки на github: java-sf-builder-simple-example



4

ти маєш на увазі щось подібне?

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

це ініціалізація списку масивів під час створення (хак)


4

Ви можете поставити деякі заяви Java як цикл для ініціалізації колекції:

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

Але ця справа впливає на продуктивність, перевірте це обговорення


4

Як вказує @Lukas Eder, слід уникати подвійних дужок ініціалізації колекцій.

Він створює анонімний внутрішній клас, і оскільки всі внутрішні класи зберігають посилання на батьківський екземпляр, він може - і 99% ймовірно - запобігти збору сміття, якщо на ці об’єкти збору посилається більше об'єктів, а не лише декларуючий.

Java 9 ввів зручні методи List.of, Set.ofі Map.of, які повинні бути використані замість. Вони швидші та ефективніші, ніж ініціалізатор подвійної дужки.


0

Перша дужка створює новий клас анонімних, а другий набір дужок створює ініціалізатори екземплярів на зразок статичного блоку.

Як і інші вказували, використовувати це не безпечно.

Однак ви завжди можете використовувати цю альтернативу для ініціалізації колекцій.

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

-1

Здається, це те саме, що ключове слово, настільки популярне у flash та vbscript. Це метод зміни того, що thisє, і нічого більше.


Не зовсім. Це могло б сказати, що створення нового класу - це метод зміни того, що thisє. Синтаксис просто створює анонімний клас (тому будь-яке посилання на thisпосилання на об'єкт цього нового анонімного класу), а потім використовує блок {...}ініціалізатора для ініціалізації новоствореного екземпляра.
усмішка
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.