Эффективность Java "Двойная инициализация скобки"? - PullRequest
760 голосов
/ 29 мая 2009

In Скрытые возможности 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 происходит то, что анонимный внутренний класс расширяет класс объекта, создаваемого оператором 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 ]

4 голосов
/ 24 декабря 2012

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

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

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

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

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

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

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

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

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

3 голосов
/ 19 июня 2014

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

3 голосов
/ 22 октября 2009

Марио Глайхман описывает , как использовать универсальные функции 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, теперь часть Гуава поддерживает аналогичную идею для построения списка. В этом интервью Джаред Леви говорит:

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

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

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

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

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

3 голосов
/ 29 мая 2009

Я второй ответ Ната, за исключением того, что я бы использовал цикл вместо создания и немедленного удаления неявного 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;
    }
2 голосов
/ 29 мая 2009
  1. Это вызовет add() для каждого участника. Если вы можете найти более эффективный способ поместить элементы в хэш-набор, используйте его. Обратите внимание, что внутренний класс, скорее всего, будет генерировать мусор, если вы чувствительны к этому.

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

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

...