Что такое синтаксис инициализации 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 000 раз по всему корпоративному приложению ... всю эту кучу памяти только для небольшого количества "синтаксического сахара"?
Если вы возьмете приведенный выше код и вернете эту карту из метода, вызывающие этого метода могут ничего не подозревать о слишком больших ресурсах, которые невозможно собрать мусором. Рассмотрим следующий пример:
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));
}
}
}
Как это устроено
Первая скобка создает новый анонимный внутренний класс. Эти внутренние классы могут получить доступ к поведению своего родительского класса. Итак, в нашем случае мы фактически создаем подкласс класса HashSet, поэтому этот внутренний класс может использовать метод put ().
И второй набор скобок - не что иное, как инициализаторы экземпляра. Если вы напоминаете основные концепции Java, то вы можете легко связать блоки инициализатора экземпляра со статическими инициализаторами из-за сходной фигурной скобки, например struct Разница лишь в том, что статический инициализатор добавляется с ключевым словом static и запускается только один раз; независимо от того, сколько объектов вы создаете.
Для забавного применения двойной инициализации см. Здесь Массив Двемти в 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 не существует такой вещи, как «инициализация двойной скобки» . На сайте Oracle нет этого термина. В этом примере используются две функции: анонимный класс и блок инициализатора. Похоже, что старый блок инициализатора был забыт разработчиками и вызывает некоторую путаницу в этой теме. Цитирование из документов Oracle :
Блоки инициализатора для переменных экземпляра выглядят так же, как статические блоки инициализатора, но без ключевого слова static:
{
// 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");
Похоже, это то же самое, что ключевое слово with, столь популярное во flash и vbscript. Это метод изменения того, что this
есть, и ничего более.
this
есть. Синтаксис просто создает анонимный класс (поэтому любая ссылка на него this
будет ссылаться на объект этого нового анонимного класса), а затем использует блок {...}
инициализатора для инициализации вновь созданного экземпляра.