Що таке синтаксис ініціалізації Double Brace ( {{ ... }}
) у Java?
Що таке синтаксис ініціалізації Double Brace ( {{ ... }}
) у Java?
Відповіді:
Подвійна дужка ініціалізації створює анонімний клас, отриманий із зазначеного класу ( зовнішні дужки), та забезпечує блок ініціалізатора всередині цього класу ( внутрішні дужки). напр
new ArrayList<Integer>() {{
add(1);
add(2);
}};
Зауважте, що ефект використання цієї подвійної дужки ініціалізації полягає в тому, що ви створюєте анонімні внутрішні класи. Створений клас має неявний this
вказівник на навколишній зовнішній клас. Хоча це зазвичай не є проблемою, це може викликати горе за деяких обставин, наприклад, при серіалізації чи збиранні сміття, і про це варто знати.
Кожного разу, коли хтось використовує подвійну брекет-ініціалізацію, кошеня вбивається.
Окрім того, що синтаксис є досить незвичним і не дуже ідіоматичним (звичайно, смак є дискусійним), ви зайво створюєте дві суттєві проблеми у вашій програмі, про які я нещодавно детальніше розповів тут .
Кожен раз, коли ви використовуєте подвійну дужку ініціалізації, робиться новий клас. Наприклад, такий приклад:
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 тис. Разів протягом свого корпоративного додатку ... вся ця купа пам’яті лише за трохи «синтаксичного цукру»?
Якщо ви берете вищевказаний код і повертаєте цю карту з методу, абоненти цього методу можуть нічого не підозрювати на дуже важких ресурсах, які неможливо зібрати сміття. Розглянемо наступний приклад:
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/
Щоб відповісти на ваше актуальне запитання, люди використовують цей синтаксис, роблячи вигляд, що у Java є щось на зразок літеральних карт, подібних до існуючих літералів масиву:
String[] array = { "John", "Doe" };
Map map = new HashMap() {{ put("John", "Doe"); }};
Деякі люди можуть вважати це синтаксично стимулюючим.
{{...}}
і оголошується як static
поле, не повинно бути ніякого можливого витоку пам'яті, лише один анонімний клас і відсутні вкладені посилання екземплярів, правда?
Map.of()
для цього є, тож це буде кращим рішенням
ReallyHeavyObject
. Крім того, анонімні внутрішні класи захоплюють усі локальні змінні, що використовуються в тілі класу, тому якщо ви використовуєте не тільки константи для ініціалізації колекцій чи карт із цим шаблоном, то екземпляри внутрішнього класу захоплюють усі вони та продовжуватимуть посилатися на них навіть тоді, коли їх фактично видалено колекцію чи карту. Отже, цим випадкам не тільки потрібно вдвічі більше необхідної пам’яті для посилань, але й ще одна витік пам’яті.
Наприклад:
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, то ви можете легко асоціювати блоки ініціалізаторів екземплярів зі статичними ініціалізаторами завдяки подібній дузі, як структура. Різниця полягає лише в тому, що статичний ініціалізатор додається до статичного ключового слова і запускається лише один раз; незалежно від того, скільки об’єктів ви створюєте.
Для веселого застосування подвійної дужки ініціалізації дивіться тут масив Дьютіті на 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
... і грубого бекону!
Я думаю, що важливо підкреслити, що у Java немає такого поняття, як "подвійна ініціалізація Brace" . Веб-сайт Oracle не має цього терміна. У цьому прикладі є дві функції, які використовуються разом: анонімний клас та блок ініціалізатора. Схоже, старий блок ініціалізатора забули розробники і викликають певну плутанину в цій темі. Цитування з документів Oracle :
Блоки ініціалізатора, наприклад, змінні виглядають як блоки статичного ініціалізатора, але без статичного ключового слова:
{
// whatever code is needed for initialization goes here
}
1- Немає такого поняття, як подвійні дужки:
я хотів би зазначити, що немає подвійної ініціалізації дужок . Існує лише звичайний традиційний блок ініціалізації брекетів. Другий брекет-блок не має нічого спільного з ініціалізацією. Відповіді кажуть, що ці два дужки щось ініціалізують, але це не так.
2- Не йдеться лише про анонімні класи, але про всі класи:
майже всі відповіді говорять про те, що це річ, яка використовується при створенні анонімних внутрішніх класів. Я думаю, що у людей, які читають ці відповіді, складеться враження, що це використовується лише під час створення анонімних внутрішніх класів. Але він використовується у всіх класах. Читання цих відповідей виглядає так - це абсолютно нова особливість, присвячена анонімним класам, і я думаю, що це вводить в оману.
3- Мета полягає лише у розміщенні дужок один за одним, а не новою концепцією:
далі, це питання говорить про ситуацію, коли друга дужка, що відкривається, знаходиться відразу після першого відкриття дужки. При використанні в звичайному класі зазвичай є код між двома дужками, але це абсолютно одне і те ж. Тож справа в розміщенні дужок. Тому я думаю, що ми не повинні говорити, що це якась нова захоплююча річ, адже це та річ, яку ми всі знаємо, а лише написана з деяким кодом між дужками. Ми не повинні створювати нову концепцію, яка називається "подвійною ініціалізацією дужок".
4- Створення вкладених анонімних класів не має нічого спільного з двома дужками:
я не згоден з аргументом того, що ви створюєте занадто багато анонімних класів. Ви створюєте їх не через ініціалізаційний блок, а просто тому, що ви їх створюєте. Вони будуть створені, навіть якщо б ви не використовували дві ініціалізації брекетів, щоб ці проблеми виникали навіть без ініціалізації ... Ініціалізація не є фактором, який створює ініціалізовані об'єкти.
Крім того, ми не повинні говорити про проблему, створену за допомогою цієї неіснуючої речі "подвійної ініціалізації дужок" або навіть звичайної ініціалізації однієї дужки, оскільки описані проблеми існують лише через створення анонімного класу, тому це не має нічого спільного з оригінальним запитанням. Але всі відповіді створюють у читачів враження, що виною не є створення анонімних класів, а ця зла (неіснуюча) річ, яка називається "подвійною ініціалізацією дужок".
Щоб уникнути всіх негативних ефектів подвійної дужки ініціалізації, таких як:
робити наступні дії:
Приклад:
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();
Переваги:
Недоліки:
І, як наслідок, у нас найпростіший шаблон для побудови Java.
Перегляньте всі зразки на github: java-sf-builder-simple-example
Це - серед інших застосувань - ярлик для ініціалізації колекцій. Вивчайте більше ...
Ви можете поставити деякі заяви 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));
}
};
Як вказує @Lukas Eder, слід уникати подвійних дужок ініціалізації колекцій.
Він створює анонімний внутрішній клас, і оскільки всі внутрішні класи зберігають посилання на батьківський екземпляр, він може - і 99% ймовірно - запобігти збору сміття, якщо на ці об’єкти збору посилається більше об'єктів, а не лише декларуючий.
Java 9 ввів зручні методи List.of
, Set.of
і Map.of
, які повинні бути використані замість. Вони швидші та ефективніші, ніж ініціалізатор подвійної дужки.
Перша дужка створює новий клас анонімних, а другий набір дужок створює ініціалізатори екземплярів на зразок статичного блоку.
Як і інші вказували, використовувати це не безпечно.
Однак ви завжди можете використовувати цю альтернативу для ініціалізації колекцій.
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
List<String> list = List.of("A", "B", "C");
Здається, це те саме, що ключове слово, настільки популярне у flash та vbscript. Це метод зміни того, що this
є, і нічого більше.
this
є. Синтаксис просто створює анонімний клас (тому будь-яке посилання на this
посилання на об'єкт цього нового анонімного класу), а потім використовує блок {...}
ініціалізатора для ініціалізації новоствореного екземпляра.