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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

15 ответов

Решение

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

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

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


Предложение Коллекционных Литералов Джошуа Блоха для 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 s и 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 программа тестирования инициализации двойной фигурной скобки.

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

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

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

Так работают внутренние классы. Они получают свои собственные 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){

            }

        };
    }

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

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

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

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

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

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

Принимая следующий тестовый класс:

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 Статические инициализаторы):

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

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

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.

подвержен утечкам

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Пример в вопросе становится:

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

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

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

Подверженный ошибкам теперь помечает этот анти-шаблон.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 Collections, теперь часть Guava, поддерживает аналогичную идею для построения списка. В этом интервью Джаред Леви говорит:

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

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

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

10.07.2014: Если бы это было так просто, как в Python:

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

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

Я второй ответ Ната, за исключением того, что я бы использовал цикл вместо создания и немедленного удаления неявного List из asList(elements):

static public Set<T> setOf(T ... elements) {
    Set set=new HashSet<T>(elements.size());
    for(T elm: elements) { set.add(elm); }
    return set;
    }
  1. Это позвонит add() для каждого члена. Если вы можете найти более эффективный способ поместить элементы в хэш-набор, используйте его. Обратите внимание, что внутренний класс, скорее всего, будет генерировать мусор, если вы чувствительны к этому.

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

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

Другие вопросы по тегам