Эффективность Java «Инициализация двойного брекета»?
В скрытых характеристиках Java в верхнем ответе упоминается Double Brace Initialization , с очень заманчивым синтаксисом:
Set flavors = new HashSet() {{ add("vanilla"); add("strawberry"); add("chocolate"); add("butter pecan"); }};
Эта идиома создает анонимный внутренний class с только инициализатором экземпляра в нем, который «может использовать любые методы […] в области содержимого».
Главный вопрос: насколько это звучит неэффективно ? Должно ли его использование ограничиваться одноразовыми инициализациями? (И, конечно же, хвастаюсь!)
- Почему memcmp намного быстрее, чем проверка цикла?
- Улучшение отражения производительности, какие альтернативы мне следует учитывать
- Какова стоимость выполнения виртуального метода в classе C ++?
- Как написать супер-быстрый файл-streamовый код в C #?
- Эффективный математический алгоритм для вычисления пересечений
Второй вопрос: новый HashSet должен быть «этим», используемым в инициализаторе экземпляра … может ли кто-нибудь пролить свет на механизм?
Третий вопрос: эта идиома слишком непонятна для использования в производственном коде?
Резюме: Очень, очень хорошие ответы, спасибо всем. По вопросу (3) люди чувствовали, что синтаксис должен быть ясным (хотя я бы рекомендовал случайный комментарий, особенно если ваш код передаст разработчикам, которые могут быть не знакомы с ним).
По вопросу (1) сгенерированный код должен выполняться быстро. Дополнительные файлы .class действительно вызывают беспорядок в файле jar и медленный запуск программы (благодаря @coobird для измерения этого). @Thilo указала, что garbage collection может быть затронут, а стоимость памяти для дополнительных загруженных classов может быть фактором в некоторых случаях.
Вопрос (2) оказался для меня самым интересным. Если я понимаю ответы, то, что происходит в DBI, заключается в том, что анонимный внутренний class расширяет class объекта, создаваемого новым оператором, и, следовательно, имеет «это» значение, ссылающееся на создаваемый экземпляр. Очень аккуратный.
В целом, DBI поражает меня как нечто интеллектуальное любопытство. Coobird и другие отмечают, что вы можете добиться такого же эффекта с помощью методов Arrays.asList, varargs, коллекций Google и предлагаемых литератур Java 7 Collection. Новые языки JVM, такие как Scala, JRuby и Groovy, также предлагают сжатые обозначения для построения списков и хорошо взаимодействуют с Java. Учитывая, что DBI загромождает путь к classам, замедляет загрузку classов немного, и делает код еще более неясным, я бы, вероятно, уклонился от него. Тем не менее, я планирую поднять это на друга, который только что получил свой SCJP и любит добродушные шутки о семантике Java! 😉 Всем спасибо!
7/2017: Baeldung имеет хорошее резюме инициализации двойной скобки и считает ее анти-шаблон.
12/2017: @Basil Bourque отмечает, что в новой Java 9 вы можете сказать:
Set flavors = Set.of("vanilla", "strawberry", "chocolate", "butter pecan");
Это точно путь. Если вы придерживаетесь более ранней версии, взгляните на ImmutableSet Google Collections .
- Сравнение двух байтовых массивов в .NET.
- Big O, как вы его вычисляете / приближаете?
- Minify Html выход приложения ASP.NET
- Является ли язык встроенной сборки медленнее, чем собственный код на C ++?
- Соображения производительности для keySet () и entrySet () карты
- Повысьте производительность webView (должна быть такая же производительность, что и собственный веб-браузер)
- Получить экземпляр нового объекта из типа
- Почему условный ход не уязвим для отказа от ветвления?
Вот проблема, когда я слишком увлекся анонимными внутренними classами:
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ы, которые были сгенерированы при создании простого приложения, и использовали обильные количества анонимных внутренних classов – каждый class будет скомпилирован в отдельный файл class
.
«Инициализация двойной скобки», как уже упоминалось, является анонимным внутренним classом с блоком инициализации экземпляра, что означает, что для каждой «инициализации» создается новый class, все с целью создания единого объекта.
Учитывая, что виртуальной машине Java необходимо будет прочитать все эти classы при их использовании, что может привести к некоторому времени в процессе проверки байт-кода и тому подобное. Не говоря уже о увеличении необходимого дискового пространства для хранения всех этих файлов class
.
Кажется, что есть немного накладных расходов при использовании инициализации с двойной привязкой, поэтому, вероятно, не такая хорошая идея пойти слишком за бортом с ней. Но, как заметил Эдди в комментариях, невозможно быть абсолютно уверенным в этом.
Для справки, двойная скобка инициализации выглядит следующим образом:
List list = new ArrayList () {{ add("Hello"); add("World!"); }};
Это похоже на «скрытую» функцию Java, но это просто переписывание:
List list = new ArrayList () { // Instance initialization block { add("Hello"); add("World!"); } };
Таким образом, это в основном блок инициализации экземпляра, который является частью анонимного внутреннего classа .
Предложение коллекции Джошуа Блоха по литературе для проектной монеты было следующим:
List intList = [1, 2, 3, 4]; Set strSet = {"Apple", "Banana", "Cactus"}; Map truthMap = { "answer" : 42 };
К сожалению, он не пробивался ни в Java 7, ни в 8, и был отложен на неопределенный срок.
эксперимент
Вот простой эксперимент, который я тестировал – сделайте 1000 ArrayList
с элементами "Hello"
и "World!"
добавляется к ним с помощью метода add
, используя два метода:
Метод 1: Инициализация двойной скобки
List l = new ArrayList () {{ add("Hello"); add("World!"); }};
Способ 2. Создайте экземпляр ArrayList
и add
List l = new ArrayList (); l.add("Hello"); l.add("World!");
Я создал простую программу для записи исходного файла Java для выполнения 1000 инициализаций с использованием двух методов:
Тест 1:
class Test1 { public static void main(String[] s) { long st = System.currentTimeMillis(); List l0 = new ArrayList () {{ add("Hello"); add("World!"); }}; List l1 = new ArrayList () {{ add("Hello"); add("World!"); }}; /* snip */ List l999 = new ArrayList () {{ add("Hello"); add("World!"); }}; System.out.println(System.currentTimeMillis() - st); } }
Тест 2:
class Test2 { public static void main(String[] s) { long st = System.currentTimeMillis(); List l0 = new ArrayList (); l0.add("Hello"); l0.add("World!"); List l1 = new ArrayList (); l1.add("Hello"); l1.add("World!"); /* snip */ List l999 = new ArrayList (); l999.add("Hello"); l999.add("World!"); System.out.println(System.currentTimeMillis() - st); } }
Обратите внимание, что прошедшее время для инициализации 1000 ArrayList
s и 1000 анонимных внутренних classов, расширяющих 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.
Одним из свойств этого подхода, который пока не был указан, является то, что, поскольку вы создаете внутренние classы, весь содержащийся class захватывается в своей области. Это означает, что до тех пор, пока ваш Set будет жив, он сохранит указатель на содержащий экземпляр ( this$0
) и сохранит его от сбора мусора, что может быть проблемой.
Это и тот факт, что новый class создается в первую очередь, хотя обычный HashSet будет работать просто отлично (или даже лучше), заставляет меня не хотеть использовать эту конструкцию (хотя я действительно долго синтаксический сахар).
Второй вопрос: новый HashSet должен быть «этим», используемым в инициализаторе экземпляра … может ли кто-нибудь пролить свет на механизм? Я бы наивно ожидал, что «это» относится к объекту, инициализирующему «ароматы».
Это то, как работают внутренние classы. Они получают свое собственное, но у них также есть указатели на родительский экземпляр, так что вы также можете вызывать методы на содержащем объекте. В случае конфликта именования внутренний class (в вашем случае HashSet) имеет приоритет, но вы можете префикс «this» с именем classа, чтобы получить внешний метод.
public class Test { public void add(Object o) { } public Set makeSet() { return new HashSet () { { add("hello"); // HashSet Test.this.add("hello"); // outer instance } }; } }
Чтобы быть понятным в создании анонимного подclassа, вы также можете определить методы. Например, переопределить HashSet.add()
public Set makeSet() { return new HashSet () { { add("hello"); // not HashSet anymore ... } @Override boolean add(String s){ } }; }
Каждый раз, когда кто-то использует двойную привязку, котенка убивают.
Помимо синтаксиса, довольно необычного и не очень идиоматического (конечно, дискуссионный вкус, конечно), вы без необходимости создаете две существенные проблемы в своем приложении, о которых я недавно рассказывал более подробно здесь .
1. Вы создаете слишком много анонимных classов
Каждый раз, когда вы используете инициализацию двойной скобки, создается новый class. Например, этот пример:
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"); }}); }}); }};
… будет производить эти classы:
Test$1$1$1.class Test$1$1$2.class Test$1$1.class Test$1.class Test.class
Это довольно немного накладных расходов для вашего загрузчика 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; } }
ReallyHeavyObject
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"); }};
Некоторые люди могут найти это синтаксически стимулирующее.
Принимая следующий тестовый class:
public class Test { public void test() { Set flavors = new HashSet () {{ add("vanilla"); add("strawberry"); add("chocolate"); add("butter pecan"); }}; } }
а затем декомпилировать файл classа, я вижу:
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 отвечает приведенный выше код: вы находитесь внутри неявного конструктора (и инициализатора экземпляра) для вашего внутреннего classа, поэтому « this
» относится к этому внутреннему classу.
Да, этот синтаксис неясен, но комментарий может прояснить неявное использование синтаксиса. Чтобы прояснить синтаксис, большинство людей знакомы со статическим блоком инициализатора (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 flavors = new HashSet () { { add("vanilla"); add("strawberry"); add("chocolate"); add("butter pecan"); } }; }
Блок кода между внутренними скобками превращается в конструктор компилятором. Внешние самые фигурные скобки ограничивают анонимный внутренний class. Чтобы сделать это последним шагом, сделав все не анонимным:
public void test() { Set flavors = new MyHashSet(); } class MyHashSet extends HashSet () { public MyHashSet() { add("vanilla"); add("strawberry"); add("chocolate"); add("butter pecan"); } }
Для целей инициализации я бы сказал, что нет накладных расходов (или настолько малых, что их можно пренебречь). Тем не менее, каждое использование flavors
будет идти не против HashSet
а вместо MyHashSet
. Вероятно, это небольшое (и, возможно, незначительное) накладные расходы. Но снова, прежде чем я волновался об этом, я бы прокомментировал это.
Опять же, на ваш вопрос №2, приведенный выше код является логическим и явным эквивалентом инициализации двойной привязки, и это делает очевидным, где this
относится: к внутреннему classу, который расширяет HashSet
.
Если у вас есть вопросы о деталях инициализаторов экземпляра, ознакомьтесь с данными в документации JLS .
склонность к утечке
Я решил перезвонить. Влияние производительности включает в себя: работу с дисками + unzip (для jar), проверку classа, пространство perm-gen (для JVM Sun Hotspot). Однако, хуже всего: это склонность к утечке. Вы не можете просто вернуться.
Set getFlavors(){ return Collections.unmodifiableSet(flavors) }
Поэтому, если набор переходит в любую другую часть, загруженную другим загрузчиком classов, и здесь хранится ссылка, будет уничтожено все дерево classов + classloader. Чтобы избежать этого, необходима копия в HashMap, new LinkedHashSet(new ArrayList(){{add("xxx);add("yyy");}})
. Не так мило. Я не использую идиому , я сам, а это как new LinkedHashSet(Arrays.asList("xxx","YYY"));
Загрузка многих classов может добавить несколько миллисекунд к началу. Если запуск не столь критичен, и вы смотрите на эффективность classов после запуска, нет никакой разницы.
package vanilla.java.perfeg.doublebracket; import java.util.*; /** * @author plawrey */ public class DoubleBracketMain { public static void main(String... args) { final List list1 = new ArrayList () { { add("Hello"); add("World"); add("!!!"); } }; List list2 = new ArrayList (list1); Set set1 = new LinkedHashSet () { { addAll(list1); } }; Set set2 = new LinkedHashSet (); set2.addAll(list1); Map map1 = new LinkedHashMap() { { put(1, "one"); put(2, "two"); put(3, "three"); } }; Map map2 = new LinkedHashMap(); 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 setOf(T ... elements) { return new HashSet (Arrays.asList(elements)); }
В библиотеке коллекций Google есть много удобных методов, таких как это, а также множество других полезных функций.
Что касается безвестности идиомы, я встречаюсь с ней и постоянно использую ее в производственном коде. Меня больше беспокоят программисты, которые путаются, когда идиома разрешается писать производственный код.
Эффективность в стороне, я редко нахожусь в желании создать декларативное собрание вне модульных тестов. Я верю, что синтаксис двойной привязки очень читабельны.
Другим способом достижения декларативного построения списков является использование Arrays.asList(T ...)
следующим образом:
List aList = Arrays.asList("vanilla", "strawberry", "chocolate");
Ограничение этого подхода, конечно, заключается в том, что вы не можете управлять конкретным типом списка, который должен быть сгенерирован.
В целом нет ничего особенно неэффективного. Для JVM обычно не имеет значения, что вы создали подclass и добавили к нему конструктор – это обычная повседневная работа на объектно-ориентированном языке. Я могу придумать довольно надуманные случаи, когда вы можете вызвать неэффективность, делая это (например, у вас есть многократно называемый метод, который заканчивается тем, что он смешивает разные classы из-за этого подclassа, тогда как обычный class, который прошел, будет полностью предсказуемым, – в последнем случае компилятор JIT мог бы сделать оптимизацию, которая невозможна в первом). Но на самом деле, я думаю, что случаи, когда это имеет значение, очень надуманны.
Я бы увидел проблему более с точки зрения того, хотите ли вы «загромождать вещи» множеством анонимных classов. В качестве приблизительного руководства рассмотрите использование идиомы не более, чем использовать, скажем, анонимные classы для обработчиков событий.
В (2) вы находитесь внутри конструктора объекта, поэтому «this» относится к объекту, который вы создаете. Это ничем не отличается от любого другого конструктора.
Что касается (3), это действительно зависит от того, кто поддерживает ваш код, я думаю. Если вы не знаете этого заранее, то тест, который я бы предложил использовать, – «видите ли вы это в исходном коде JDK?» (в этом случае я не помню, чтобы видел много анонимных инициализаторов, и, конечно же, не в тех случаях, когда это единственный контент анонимного classа). В большинстве проектов с умеренным размером, я бы сказал, что вам действительно понадобятся ваши программисты, чтобы понять источник JDK в какой-то момент, поэтому любой синтаксис или идиома, используемые там, являются «честной игрой». Помимо этого, я бы сказал, тренируйте людей по этому синтаксису, если у вас есть контроль над тем, кто поддерживает код, иначе комментировать или избегать.
Я изучал это и решил сделать более глубокий тест, чем тот, который был предоставлен действительным ответом.
Вот код: https://gist.github.com/4368924
и это мое заключение
Я с удивлением обнаружил, что в большинстве тестовых тестов внутреннее инициирование было фактически быстрее (в некоторых случаях это почти удвоение). При работе с большими числами преимущество, похоже, исчезает.
Интересно, что случай, который создает 3 объекта в цикле, теряет выгоду, раньше, чем в других случаях. Я не уверен, почему это происходит, и нужно провести больше испытаний, чтобы сделать какие-либо выводы. Создание конкретных реализаций может помочь избежать перезагрузки определения classа (если это происходит)
Тем не менее, ясно, что в большинстве случаев не так много накладных расходов для отдельного объекта, даже при большом количестве.
Один из вариантов назад будет тот факт, что каждое из инициалов двойной привязки создает новый файл classа, который добавляет весь блок диска к размеру нашего приложения (или около 1 к при сжатии). Небольшой размер, но если он используется во многих местах, он может потенциально повлиять. Используйте это 1000 раз, и вы потенциально добавляете к вам приложение MiB, которое может быть связано со встроенной средой.
Мой вывод? Его можно использовать, если он не подвергается насилию.
Дайте мне знать, что вы думаете 🙂
Я отвечу на второй Нэт, но я бы использовал цикл вместо создания и немедленного броска неявного списка из asList (элементов):
static public Set setOf(T ... elements) { Set set=new HashSet (elements.size()); for(T elm: elements) { set.add(elm); } return set; }
Хотя этот синтаксис может быть удобным, он также добавляет много этих ссылок на $ 0, поскольку они становятся вложенными, и может быть сложно отладить отладку в инициализаторы, если на каждой из них не установлены контрольные точки. По этой причине я рекомендую использовать это только для банальных сеттеров, особенно для констант и мест, где анонимные подclassы не имеют значения (например, без использования сериализации).
Марио Глейхман описывает, как использовать общие функции Java 1.5 для моделирования литералов Scala List, хотя, к сожалению, вы заканчиваете неизменными списками.
Он определяет этот class:
package literal; public class collection { public static List List(T...elems){ return Arrays.asList( elems ); } }
и использует его таким образом:
import static literal.collection.List; import static system.io.*; public class CollectionDemo { public void demoList(){ List slist = List( "a", "b", "c" ); List iList = List( 1, 2, 3 ); for( String elem : List( "a", "java", "list" ) ) System.out.println( elem ); } }
Коллекции Google, теперь часть Guava поддерживает аналогичную идею для построения списка. В этом интервью Джаред Леви говорит:
[…] наиболее интенсивно используемые функции, которые появляются почти во всех classах Java, которые я пишу, являются статическими методами, которые уменьшают количество повторяющихся нажатий клавиш в вашем Java-коде. Это настолько удобно, что вы можете вводить команды следующим образом:
Map
= Maps.newHashMap();
List
animals = Lists.immutableList("cat", "dog", "horse");
7/10/2014: Если бы это было так просто, как Python’s:
animals = ['cat', 'dog', 'horse']
Инициализация с двойной привязкой – ненужный взлом, который может привести к утечкам памяти и другим проблемам
Нет никаких законных оснований использовать этот «трюк». Guava предоставляет отличные неизменные коллекции, которые include как статические фабрики, так и строители, позволяя вам заполнить свою коллекцию, где она объявлена в чистом, понятном и безопасном синтаксисе.
Пример в вопросе:
Set flavors = ImmutableSet.of( "vanilla", "strawberry", "chocolate", "butter pecan");
Мало того, что это более короткое и легкое для чтения, но оно позволяет избежать многочисленных проблем с двойным шаблоном, описанным в других ответах . Конечно, он работает аналогично непосредственно сконструированному HashMap
, но он опасен и подвержен ошибкам, и есть лучшие варианты.
Каждый раз, когда вы обнаруживаете, что рассматриваете двойную привязку к инициализации, вы должны пересмотреть свои API или ввести новые, чтобы правильно решить проблему, а не использовать синтаксические трюки.
В настоящее время флажок «Прогнозирование ошибок» обозначает этот анти-шаблон .
-
Это вызовет
add()
для каждого члена. Если вы можете найти более эффективный способ поместить элементы в хеш-набор, используйте его. Обратите внимание, что внутренний class, скорее всего, сгенерирует мусор, если вы чувствительны к этому. -
It seems to me as if the context is the object returned by
new
, which is theHashSet
. -
If you need to ask… More likely: will the people who come after you know this or not? Is it easy to understand and explain? If you can answer “yes” to both, feel free to use it.