Эффективность Java "Инициализация двойного брекета"?

В Скрытые особенности Java в верхнем ответе упоминается Двойная инициализация брекетов, с очень заманчивый синтаксис:

Set<String> flavors = new HashSet<String>() {{
    add("vanilla");
    add("strawberry");
    add("chocolate");
    add("butter pecan");
}};

Эта идиома создает анонимный внутренний класс с только инициализатором экземпляра в нем, который "может использовать любые методы [...] в области содержимого".

Основной вопрос: насколько это звучит как неэффективно? Должно ли его использование ограничиваться одноразовыми инициализациями? (И, конечно, хвастаюсь!)

Второй вопрос: новый HashSet должен быть "this", используемый в инициализаторе экземпляра... может ли кто-нибудь пролить свет на механизм?

Третий вопрос: эта идиома слишком неясна для использования в производственном коде?

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

В вопросе (1) сгенерированный код должен выполняться быстро. Дополнительные файлы .class действительно вызывают беспорядок в файле jar и медленный запуск программы (благодаря @coobird для измерения этого). @Thilo указала, что сбор мусора может быть затронут, а стоимость памяти для дополнительных загруженных классов может быть фактором в некоторых случаях.

Вопрос (2) оказался для меня самым интересным. Если я понимаю ответы, то, что происходит в DBI, заключается в том, что анонимный внутренний класс расширяет класс объекта, создаваемого новым оператором, и, следовательно, имеет значение "this", ссылающееся на создаваемый экземпляр. Очень аккуратно.

В целом, DBI поражает меня как нечто интеллектуальное любопытство. Coobird и другие отмечают, что вы можете добиться такого же эффекта с помощью методов Arrays.asList, varargs, коллекций Google и предлагаемых литератур Java 7 Collection. Новые языки JVM, такие как Scala, JRuby и Groovy, также предлагают краткие обозначения для построения списка и хорошо взаимодействуют с Java. Учитывая, что DBI загромождает путь к классам, замедляет загрузку классов немного, и делает код еще более неясным, я бы, вероятно, уклонился от него. Тем не менее, я планирую spring это на другом, который только что получил свой SCJP и любит добродушные шутки о семантике Java!;-) Спасибо всем!

7/2017: Baeldung имеет хорошее резюме инициализации двойной скобки и считает его анти-шаблоном.

12/2017: @Basil Bourque отмечает, что в новой Java 9 вы можете сказать:

Set<String> flavors = Set.of("vanilla", "strawberry", "chocolate", "butter pecan");

Это точно путь. Если вы застряли в более ранней версии, посмотрите ImmutableSet Google Collections.

Ответ 1

Вот проблема, когда я слишком увлекаюсь анонимными внутренними классами:

2009/05/27  16:35             1,602 DemoApp2$1.class
2009/05/27  16:35             1,976 DemoApp2$10.class
2009/05/27  16:35             1,919 DemoApp2$11.class
2009/05/27  16:35             2,404 DemoApp2$12.class
2009/05/27  16:35             1,197 DemoApp2$13.class

/* snip */

2009/05/27  16:35             1,953 DemoApp2$30.class
2009/05/27  16:35             1,910 DemoApp2$31.class
2009/05/27  16:35             2,007 DemoApp2$32.class
2009/05/27  16:35               926 DemoApp2$33$1$1.class
2009/05/27  16:35             4,104 DemoApp2$33$1.class
2009/05/27  16:35             2,849 DemoApp2$33.class
2009/05/27  16:35               926 DemoApp2$34$1$1.class
2009/05/27  16:35             4,234 DemoApp2$34$1.class
2009/05/27  16:35             2,849 DemoApp2$34.class

/* snip */

2009/05/27  16:35               614 DemoApp2$40.class
2009/05/27  16:35             2,344 DemoApp2$5.class
2009/05/27  16:35             1,551 DemoApp2$6.class
2009/05/27  16:35             1,604 DemoApp2$7.class
2009/05/27  16:35             1,809 DemoApp2$8.class
2009/05/27  16:35             2,022 DemoApp2$9.class

Это все классы, которые были сгенерированы, когда я создавал простое приложение, и использовали большое количество анонимных внутренних классов - каждый класс будет скомпилирован в отдельный файл class.

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

Учитывая, что виртуальной машине Java нужно будет читать все эти классы при их использовании, это может привести к некоторому времени в процессе проверки байт-кода и так далее. Не говоря уже об увеличении необходимого дискового пространства для хранения всех этих файлов class.

Кажется, что при использовании инициализации с двойной скобкой есть некоторые издержки, так что, вероятно, не слишком хорошая идея, чтобы слишком зацикливаться на этом. Но, как отметил Эдди в комментариях, невозможно быть абсолютно уверенным в воздействии.


Для справки: инициализация двойной скобки следующая:

List<String> list = new ArrayList<String>() {{
    add("Hello");
    add("World!");
}};

Это похоже на "скрытую" особенность Java, но это просто переписывание:

List<String> list = new ArrayList<String>() {

    // Instance initialization block
    {
        add("Hello");
        add("World!");
    }
};

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


Предложение Джошуа Блоха Collection Literals для Project Coin было следующим:

List<Integer> intList = [1, 2, 3, 4];

Set<String> strSet = {"Apple", "Banana", "Cactus"};

Map<String, Integer> truthMap = { "answer" : 42 };

К сожалению, он не пробился ни в Java 7, ни в 8 и был отложен на неопределенный срок.


Эксперимент

Вот простой эксперимент, который я протестировал - сделайте 1000 ArrayList с элементами "Hello" и "World!", добавленными к ним с помощью метода add, используя два метода:

Метод 1: инициализация двойной скобки

List<String> l = new ArrayList<String>() {{
  add("Hello");
  add("World!");
}};

Метод 2: создание ArrayList и add

List<String> l = new ArrayList<String>();
l.add("Hello");
l.add("World!");

Я создал простую программу для записи исходного файла Java для выполнения 1000 инициализаций, используя два метода:

Тест 1:

class Test1 {
  public static void main(String[] s) {
    long st = System.currentTimeMillis();

    List<String> l0 = new ArrayList<String>() {{
      add("Hello");
      add("World!");
    }};

    List<String> l1 = new ArrayList<String>() {{
      add("Hello");
      add("World!");
    }};

    /* snip */

    List<String> l999 = new ArrayList<String>() {{
      add("Hello");
      add("World!");
    }};

    System.out.println(System.currentTimeMillis() - st);
  }
}

Тест 2:

class Test2 {
  public static void main(String[] s) {
    long st = System.currentTimeMillis();

    List<String> l0 = new ArrayList<String>();
    l0.add("Hello");
    l0.add("World!");

    List<String> l1 = new ArrayList<String>();
    l1.add("Hello");
    l1.add("World!");

    /* snip */

    List<String> l999 = new ArrayList<String>();
    l999.add("Hello");
    l999.add("World!");

    System.out.println(System.currentTimeMillis() - st);
  }
}

Обратите внимание, что истекшее время для инициализации 1000 ArrayList и 1000 анонимных внутренних классов, расширяющих ArrayList, проверяется с помощью System.currentTimeMillis, поэтому таймер не имеет очень высокого разрешения. В моей системе Windows разрешение составляет около 15-16 миллисекунд.

Результаты 10 прогонов двух тестов были следующими:

Test1 Times (ms)           Test2 Times (ms)
----------------           ----------------
           187                          0
           203                          0
           203                          0
           188                          0
           188                          0
           187                          0
           203                          0
           188                          0
           188                          0
           203                          0

Как видно, инициализация двойной фигурной скобки имеет заметное время выполнения около 190 мс.

Между тем время выполнения инициализации ArrayList оказалось равным 0 мс. Конечно, следует учитывать разрешение таймера, но оно может составлять менее 15 мс.

Таким образом, кажется, есть заметная разница во времени выполнения двух методов. Похоже, что в двух методах инициализации действительно есть издержки.

И да, было 1000 файлов .class, сгенерированных при компиляции тестовой программы инициализации двойной скобки Test1.

Ответ 2

Одним из свойств этого подхода, который не был указан до сих пор, является то, что, поскольку вы создаете внутренние классы, весь содержащийся класс захватывается в своей области. Это означает, что до тех пор, пока ваш Set жив, он сохранит указатель на содержащий экземпляр (this$0) и сохранит его от сбора мусора, что может быть проблемой.

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

Второй вопрос: новый HashSet должен быть "this", используемый в инициализаторе экземпляра... может ли кто-нибудь пролить свет на механизм? Я бы наивно ожидал, что "this" ссылается на инициализацию объекта "ароматы".

Это то, как работают внутренние классы. Они получают свой собственный this, но у них также есть указатели на родительский экземпляр, так что вы также можете вызывать методы для содержащего объекта. В случае конфликта именования внутренний класс (в вашем случае HashSet) имеет приоритет, но вы можете префикс "this" с именем класса, чтобы получить внешний метод.

public class Test {

    public void add(Object o) {
    }

    public Set<String> makeSet() {
        return new HashSet<String>() {
            {
              add("hello"); // HashSet
              Test.this.add("hello"); // outer instance 
            }
        };
    }
}

Чтобы быть понятным в создании анонимного подкласса, вы также можете определить методы там. Например, переопределить HashSet.add()

    public Set<String> makeSet() {
        return new HashSet<String>() {
            {
              add("hello"); // not HashSet anymore ...
            }

            @Override
            boolean add(String s){

            }

        };
    }

Ответ 3

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

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

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

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

Ответ 4

Взяв следующий тестовый класс:

public class Test {
  public void test() {
    Set<String> flavors = new HashSet<String>() {{
        add("vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
    }};
  }
}

а затем декомпилировать файл класса, я вижу:

public class Test {
  public void test() {
    java.util.Set flavors = new HashSet() {

      final Test this$0;

      {
        this$0 = Test.this;
        super();
        add("vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
      }
    };
  }
}

Это не выглядит ужасно неэффективным для меня. Если бы я беспокоился о производительности для чего-то подобного, я бы это прокомментировал. На ваш вопроС# 2 отвечает приведенный выше код: вы находитесь внутри неявного конструктора (и инициализатора экземпляра) для вашего внутреннего класса, поэтому "this" относится к этому внутреннему классу.

Да, этот синтаксис неясен, но комментарий может прояснить неявное использование синтаксиса. Чтобы прояснить синтаксис, большинство людей знакомы со статическим блоком инициализатора (JLS 8.7 Static Initializers):

public class Sample1 {
    private static final String someVar;
    static {
        String temp = null;
        ..... // block of code setting temp
        someVar = temp;
    }
}

Вы также можете использовать аналогичный синтаксис (без слова "static" ) для использования конструктора (JLS 8.6 Instance Initializers), хотя я никогда не видел этого, используемого в производственном коде. Это гораздо менее известно.

public class Sample2 {
    private final String someVar;

    // This is an instance initializer
    {
        String temp = null;
        ..... // block of code setting temp
        someVar = temp;
    }
}

Если у вас нет конструктора по умолчанию, тогда блок кода между { и } превращается в конструктор компилятором. Имея это в виду, разгадайте код двойной скобки:

public void test() {
  Set<String> flavors = new HashSet<String>() {
      {
        add("vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
      }
  };
}

Блок кода между внутренними скобками превращается в конструктор компилятором. Внешние самые фигурные скобки ограничивают анонимный внутренний класс. Чтобы сделать это, последний шаг сделать все не анонимным:

public void test() {
  Set<String> flavors = new MyHashSet();
}

class MyHashSet extends HashSet<String>() {
    public MyHashSet() {
        add("vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
    }
}

В целях инициализации я бы сказал, что нет накладных расходов (или настолько малых, что его можно пренебречь). Однако каждое использование flavors будет идти не против HashSet, а вместо этого MyHashSet. Вероятно, это небольшое (и, возможно, незначительное) накладные расходы. Но опять же, прежде чем я волновался об этом, я бы прокомментировал его.

Опять же, на ваш вопроС# 2, приведенный выше код является логическим и явным эквивалентом инициализации двойной привязки, и это делает очевидным, где "this" относится к внутреннему классу, который расширяет HashSet.

Если у вас есть вопросы о деталях инициализаторов экземпляра, ознакомьтесь с деталями в документации JLS.

Ответ 5

склонность к утечке

Я решил перезвонить. Эффект производительности включает в себя: дисковая операция + unzip (для jar), проверка класса, пространство perm-gen (для Sun Hotspot JVM). Однако, самое худшее: он просачивается. Вы не можете просто вернуться.

Set<String> getFlavors(){
  return Collections.unmodifiableSet(flavors)
}

Итак, если набор выходит в любую другую часть, загруженную другим загрузчиком классов, и там хранится ссылка, будет уничтожено все дерево классов + загрузчик классов. Чтобы этого избежать, необходима копия в HashMap, new LinkedHashSet(new ArrayList(){{add("xxx);add("yyy");}}). Не так мило. Я сам не использую эту идиому, вместо этого она похожа на new LinkedHashSet(Arrays.asList("xxx","YYY"));

Ответ 6

Загрузка многих классов может добавить к началу несколько миллисекунд. Если запуск не столь критичен, и вы смотрите на эффективность классов после запуска, нет никакой разницы.

package vanilla.java.perfeg.doublebracket;

import java.util.*;

/**
 * @author plawrey
 */
public class DoubleBracketMain {
    public static void main(String... args) {
        final List<String> list1 = new ArrayList<String>() {
            {
                add("Hello");
                add("World");
                add("!!!");
            }
        };
        List<String> list2 = new ArrayList<String>(list1);
        Set<String> set1 = new LinkedHashSet<String>() {
            {
                addAll(list1);
            }
        };
        Set<String> set2 = new LinkedHashSet<String>();
        set2.addAll(list1);
        Map<Integer, String> map1 = new LinkedHashMap<Integer, String>() {
            {
                put(1, "one");
                put(2, "two");
                put(3, "three");
            }
        };
        Map<Integer, String> map2 = new LinkedHashMap<Integer, String>();
        map2.putAll(map1);

        for (int i = 0; i < 10; i++) {
            long dbTimes = timeComparison(list1, list1)
                    + timeComparison(set1, set1)
                    + timeComparison(map1.keySet(), map1.keySet())
                    + timeComparison(map1.values(), map1.values());
            long times = timeComparison(list2, list2)
                    + timeComparison(set2, set2)
                    + timeComparison(map2.keySet(), map2.keySet())
                    + timeComparison(map2.values(), map2.values());
            if (i > 0)
                System.out.printf("double braced collections took %,d ns and plain collections took %,d ns%n", dbTimes, times);
        }
    }

    public static long timeComparison(Collection a, Collection b) {
        long start = System.nanoTime();
        int runs = 10000000;
        for (int i = 0; i < runs; i++)
            compareCollections(a, b);
        long rate = (System.nanoTime() - start) / runs;
        return rate;
    }

    public static void compareCollections(Collection a, Collection b) {
        if (!a.equals(b) && a.hashCode() != b.hashCode() && !a.toString().equals(b.toString()))
            throw new AssertionError();
    }
}

печатает

double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 34 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns

Ответ 7

Для создания наборов вы можете использовать метод varargs factory вместо инициализации с двойной привязкой:

public static Set<T> setOf(T ... elements) {
    return new HashSet<T>(Arrays.asList(elements));
}

В библиотеке коллекций Google есть много удобных методов, таких как это, а также множество других полезных функций.

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

Ответ 8

Эффективность в стороне, я редко нахожусь в желании создать декларативное собрание вне модульных тестов. Я считаю, что синтаксис двойной привязки очень читабельны.

Другим способом достижения декларативной конструкции списков является использование Arrays.asList(T ...) следующим образом:

List<String> aList = Arrays.asList("vanilla", "strawberry", "chocolate");

Ограничение этого подхода - это, конечно, что вы не можете управлять конкретным типом списка, который должен быть сгенерирован.

Ответ 9

Там вообще ничего особенно неэффективно. Для JVM обычно не имеет значения, что вы создали подкласс и добавили к нему конструктор - что обычное повседневное дело на объектно-ориентированном языке. Я могу придумать довольно надуманные случаи, когда вы можете вызвать неэффективность, делая это (например, у вас есть многократно называемый метод, который заканчивается тем, что он смешивает разные классы из-за этого подкласса, тогда как обычный класс, который прошел, будет полностью предсказуемым, - в последнем случае компилятор JIT мог бы сделать оптимизацию, которая невозможна в первом). Но на самом деле, я думаю, что случаи, когда это имеет значение, очень надуманны.

Я вижу проблему более с точки зрения того, хотите ли вы "загромождать вещи" множеством анонимных классов. В качестве приблизительного руководства рассмотрите возможность использования идиомы не более, чем использовать, скажем, анонимные классы для обработчиков событий.

В (2) вы находитесь внутри конструктора объекта, поэтому "this" ссылается на объект, который вы создаете. Это не отличается от любого другого конструктора.

Что касается (3), это действительно зависит от того, кто поддерживает ваш код, я думаю. Если вы не знаете этого заранее, то тест, который я бы предложил использовать, - "видите ли вы это в исходном коде JDK?" (в этом случае я не помню, чтобы видел много анонимных инициализаторов, и, конечно же, не в тех случаях, когда это единственный контент анонимного класса). В большинстве проектов с умеренным размером, я бы сказал, что вам действительно понадобятся ваши программисты, чтобы понять источник JDK в какой-то момент, поэтому любой синтаксис или идиома, используемые там, являются "честной игрой". Помимо этого, я бы сказал, тренируйте людей по этому синтаксису, если у вас есть контроль над тем, кто поддерживает код, иначе комментировать или избегать.

Ответ 10

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

Нет никакой законной причины использовать этот "трюк". Guava предоставляет отличные неизменные коллекции, которые включают как статические фабрики, так и строители, позволяя вам заполнять свою коллекцию там, где она объявлена ​​в чистом, понятном и безопасном синтаксис.

Пример в вопросе будет:

Set<String> flavors = ImmutableSet.of(
    "vanilla", "strawberry", "chocolate", "butter pecan");

Мало того, что это более короткое и легкое для чтения, но оно позволяет избежать многочисленных проблем с двойным шаблоном, описанным в других ответах. Конечно, он работает аналогично прямому построению HashMap, но он опасен и подвержен ошибкам, и есть лучшие варианты.

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

Error-Prone теперь обозначает этот анти-шаблон.

Ответ 11

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

Вот код: https://gist.github.com/4368924

и это мой вывод

Я с удивлением обнаружил, что в большинстве тестовых тестов внутреннее инициирование было фактически быстрее (в некоторых случаях почти вдвое). При работе с большими числами преимущество, похоже, исчезает.

Интересно, что случай, который создает 3 объекта в цикле, теряет выгоду, раньше, чем в других случаях. Я не уверен, почему это происходит, и нужно провести больше испытаний, чтобы сделать какие-либо выводы. Создание конкретных реализаций может помочь избежать переопределения определения класса (если это происходит)

Однако ясно, что в большинстве случаев не так много накладных расходов для отдельного объекта, даже с большими числами.

Один из вариантов назад - это тот факт, что каждое из инициалов двойной привязки создает новый файл класса, который добавляет весь блок диска к размеру нашего приложения (или около 1 к при сжатии). Небольшая площадь, но если она используется во многих местах, она может потенциально повлиять. Используйте это 1000 раз, и вы потенциально добавляете к вам приложение MiB, которое может быть связано со встроенной средой.

Мой вывод? Его можно использовать, если он не подвергается насилию.

Дайте мне знать, что вы думаете:)

Ответ 12

Второй ответ Nat, за исключением того, что я использовал бы цикл вместо создания и немедленного броска неявного списка из asList (элементов):

static public Set<T> setOf(T ... elements) {
    Set set=new HashSet<T>(elements.size());
    for(T elm: elements) { set.add(elm); }
    return set;
    }

Ответ 13

Марио Глейхман описывает, как использовать общие функции Java 1.5 для имитации Scala Список литералов, хотя, к сожалению, вы заканчиваете неизменяемыми списками.

Он определяет этот класс:

package literal;

public class collection {
    public static <T> List<T> List(T...elems){
        return Arrays.asList( elems );
    }
}

и использует его таким образом:

import static literal.collection.List;
import static system.io.*;

public class CollectionDemo {
    public void demoList(){
        List<String> slist = List( "a", "b", "c" );
        List<Integer> iList = List( 1, 2, 3 );
        for( String elem : List( "a", "java", "list" ) )
            System.out.println( elem );
    }
}

Коллекции Google, теперь входящие в Guava, поддерживают аналогичную идею для построения списка. В это интервью, Джаред Леви говорит:

[...] наиболее интенсивно используемые функции, которые появляются почти во всех классах Java, которые я пишу, являются статическими методами, которые уменьшают количество повторяющихся нажатий клавиш в вашем Java-коде. Это настолько удобно, что вы можете вводить команды, как показано ниже:

Map<OneClassWithALongName, AnotherClassWithALongName> = Maps.newHashMap();

List<String> animals = Lists.immutableList("cat", "dog", "horse");

7/10/2014: Если бы это было так просто, как Python's:

animals = ['cat', 'dog', 'horse']

Ответ 14

Хотя этот синтаксис может быть удобным, он также добавляет много этих ссылок на $0, поскольку они становятся вложенными, и может быть сложно отладить отладку в инициализаторы, если на каждой из них не установлены контрольные точки. По этой причине я рекомендую использовать это только для банальных сеттеров, особенно для констант и мест, где анонимные подклассы не имеют значения (например, без использования сериализации).

Ответ 15

  1. Это вызовет add() для каждого члена. Если вы можете найти более эффективный способ поместить элементы в хеш-набор, используйте его. Обратите внимание, что внутренний класс, скорее всего, сгенерирует мусор, если вы чувствительны к этому.

  2. Мне кажется, что контекст - это объект, возвращаемый new, который является HashSet.

  3. Если вам нужно спросить... Скорее: будут ли люди, которые придут после того, как вы это знаете или нет? Легко ли это понять и объяснить? Если вы можете ответить "да" на оба, не стесняйтесь использовать его.