Что такое инициализация двойной скобки в Java?

что такое синтаксис инициализации двойной скобки ({{ ... }}) в Java?

11 ответов


двойной дубль инициализации создает анонимный класс, производный от указанного класса (внешний фигурные скобки) и предоставляет блок инициализатора в этом классе (внутренний брекеты). например,

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

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


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

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

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.class
Test.class
Test.class
Test.class
Test.class

это довольно много накладных расходов для вашего загрузчика классов-даром! Конечно, это не займет много времени инициализации, если вы сделаете это один раз. Но если вы сделаете это 20 ' 000 раз на протяжении всего вашего корпоративного приложения... вся эта куча памяти только для "синтаксического сахара"?

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. Вы, вероятно, не хотите рисковать этим:

Memory Leak Right Here

изображения от 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"); }};

некоторые люди могут найти это синтаксически стимулирования.


  • первая скобка создает новый анонимный внутренний класс.
  • второй набор скобок создает инициализаторы экземпляра, такие как статический блок в классе.

например:

   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, поэтому этот внутренний класс способен использовать метод add ().

и второй набор скобок не инициализаторы экземпляра. Если вы напомните Основные понятия java, то вы можете легко связать блоки инициализатора экземпляра со статическими инициализаторами из-за подобной скобки, такой как struct. Разница только в том, что статический инициализатор добавляется со статическим ключевым словом и запускается только один раз; независимо от того, сколько объектов вы создавать.

больше


для забавного применения двойной инициализации скобки см. здесь массив Dwemthy'S в 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 docs:

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

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

Я хотел бы отметить, что нет такой вещи, как инициализация двойной скобки. Есть только обычный традиционный бандажа initializaition блока. Второй блок фигурных скобок не имеет ничего общего с инициализацией. Ответы говорят, что эти две фигурные скобки инициализируют что-то, но это не так.

во-вторых, почти все ответы говорят, что это вещь, используемая при создании анонимных внутренних классов. Я думаю, что люди, читающие эти ответы, получат впечатление, что это только используется при создании анонимных внутренних классов. Но он используется во всех классах. Чтение этих ответов похоже на какое-то новое специальное будущее, посвященное анонимным классам, и я думаю, что это вводит в заблуждение.

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

Я не согласен с аргументом, что вы создаете слишком много анонимных классов. Вы создаете их не потому, что блок инициализации, а просто потому, что вы их создаете. Они будут созданы, даже если вы не использовали инициализацию двух фигурных скобок, поэтому эти проблемы произойдет даже без инициализации... Инициализация не является фактором, который создает инициализированный объект.

кроме того, мы не должны говорить о проблеме, созданной с помощью этой несуществующей вещи "двойная инициализация скобки" или даже обычной инициализации одной скобки, потому что описанные проблемы существуют только из-за создания анонимного класса, поэтому он не имеет ничего общего с оригинальным вопросом. Но все ответы с дают читателям впечатление, что это не вина создания анонимные классы, но эта злая (несуществующая) вещь называется "инициализация двойной скобки".


чтобы избежать всех негативных последствий двойной инициализации скобки, например:

  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 builder когда-либо.

см. все образцы на github:java-sf-builder-простой пример


Это-среди других применений-ярлык для инициализации коллекций. подробнее ...


вы можете поместить некоторые операторы 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));
    }
};

но этот случай влияет на производительность, проверьте это обсуждение


Это будет похоже на ключевое слово with, столь популярное во flash и vbscript. Это метод изменения того, что this и ничего больше.