Использование фабрик коллекций Java 9

В контексте комментариев и ответов, данных в List.of() или Collections.emptyList() и List.of(...) or Collections.unmodifiedList() Я придумал два следующих эмпирических правила (которые также применяются к фабрикам Set и Map соответственно) .

  1. Не заменяйте все вхождения

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

class Bean {
  private List<Bean> beans = Collection.emptyList();
  public List<Bean> getBeans() {
    if (beans == Collections.EMPTY_LIST) { beans = new ArrayList<>(); }
    return beans;
  }
}
  1. Используйте новые фабрики в качестве построителей аргументов метода

Используйте новые фабрики List.of() и варианты в качестве быстрой и упрощенной версии при вызове исполняемого файла с параметрами List. Вот мои текущие схемы замены:

Collections.emptyList()       --> List.of()
Collections.singletonList(a)  --> List.of(a)
Arrays.asList(a, ..., z)      --> List.of(a, ..., z)

В вымышленном использовании Collections.indexOfSubList следующие строки

Collections.indexOfSubList(Arrays.asList(1, 2, 3), Collections.emptyList());
Collections.indexOfSubList(Arrays.asList(1, 2, 3), Collections.singletonList(1));
Collections.indexOfSubList(Arrays.asList(1, 2, 3), Arrays.asList(1));
Collections.indexOfSubList(Arrays.asList(1, 2, 3), Arrays.asList(2, 3));
Collections.indexOfSubList(Arrays.asList(1, 2, 3), Arrays.asList(1, 2, 3));

буду читать

Collections.indexOfSubList(List.of(1, 2, 3), List.of());
Collections.indexOfSubList(List.of(1, 2, 3), List.of(1));
Collections.indexOfSubList(List.of(1, 2, 3), List.of(1));
Collections.indexOfSubList(List.of(1, 2, 3), List.of(2, 3));
Collections.indexOfSubList(List.of(1, 2, 3), List.of(1, 2, 3));

Вы (не) согласны?


person Sormuras    schedule 30.11.2016    source источник
comment
Какова цель if (beans == Collections.EMPTY_LIST) { beans = new ArrayList<>(); }?   -  person marstran    schedule 30.11.2016
comment
@marstran, потому что вы можете просто something.getBeans().add(somethingElse) не создавать новый список массивов на более высоком уровне и не устанавливать его и т. д.   -  person Shadov    schedule 30.11.2016


Ответы (2)


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

Есть несколько причин, по которым новые фабрики коллекций не являются простой заменой кода, который инициализирует коллекции с помощью существующих API. Очевидно, неизменность — одна из наиболее важных причин; если вам нужно изменить коллекцию позже, она, очевидно, не может быть неизменной! Но есть и другие причины, некоторые из них довольно тонкие.

Пример замены существующих API новыми API см. в разделе JDK-8134373. . Темы обзора находятся здесь: Часть 1 Часть 2.

Вот краткое изложение проблем.

Перенос массива в сравнении с копированием. Иногда у вас есть массив, например. параметр varargs, и вы хотите обработать его как список. Иногда Arrays.asList здесь подходит больше всего, так как это просто оболочка. Напротив, List.of создает копию, что может быть расточительным. С другой стороны, вызывающая сторона по-прежнему имеет дескриптор обернутого массива и может изменить его, что может быть проблемой, поэтому иногда вам нужно оплатить расходы на его копирование, например, если вы хотите сохранить ссылку на массив. список в переменной экземпляра.

Порядок итерации хешированной коллекции. Новые структуры Set.of и Map.of рандомизируют порядок итерации. Порядок итерации HashSet и HashMap не определен, но на практике оказывается относительно стабильным. В коде могут возникать непреднамеренные зависимости от порядка итерации. Переключение на новые фабрики коллекций может подвергнуть старый код зависимостям порядка итерации, выявив скрытые ошибки.

Запрет нулевых значений. Новые коллекции полностью запрещают пустые значения, в то время как обычные непараллельные коллекции (ArrayList, HashMap) их разрешают.

Формат сериализации. Новые коллекции имеют формат сериализации, отличный от старых. Если коллекция сериализована или хранится в другом сериализованном классе, сериализованный вывод будет другим. Это может быть или не быть проблемой. Но если вы планируете взаимодействовать с другими системами, это может стать проблемой. В частности, если вы передадите сериализованную форму новых коллекций на JVM Java 8, она не сможет десериализоваться, потому что новые классы не существуют в Java 8.

Строгое поведение метода мутатора. Новые коллекции неизменяемы, поэтому, конечно, они выдают UnsupportedOperationException при вызове методов мутатора. Однако есть некоторые пограничные случаи, когда поведение не является одинаковым для всех коллекций. Например,

    Collections.singletonList("").addAll(Collections.emptyList())

ничего не делает, тогда как

    List.of("").addAll(Collections.emptyList())

кинет УОЭ. В общем, новые коллекции и неизменяемые оболочки последовательно строги в выбрасывании UOE при любом вызове метода мутатора, даже если фактическая мутация не произойдет. Другие неизменяемые коллекции, например из Collections.empty* и Collections.singleton*, вызовут UOE только в том случае, если произойдет фактическая мутация.

Дубликаты. Новые фабрики Set и Map отклоняют повторяющиеся элементы и ключи. Обычно это не проблема, если вы инициализируете коллекцию списком констант. Действительно, если список констант имеет дубликаты, это, вероятно, ошибка. Потенциально это может быть проблемой, когда вызывающему объекту разрешено передавать коллекцию или массив (например, varags) элементов. Если вызывающий объект передает дубликаты, существующие API-интерфейсы молча пропускают дубликаты, тогда как новые фабрики выдают IllegalArgumentException. Это изменение поведения, которое может повлиять на вызывающих абонентов.


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

person Stuart Marks    schedule 03.12.2016
comment
Спасибо за ответ, Стюарт. Если кому-то нужны еще подробности по этой теме, посмотрите «Коллекции, заправленные Стюартом Марксом»: youtube.com/watch?v=LgR9ByD1dEw - person Sormuras; 07.12.2016
comment
@Sormuras Спасибо за ссылку на видео моего выступления! Это из JavaOne 2016. - person Stuart Marks; 07.12.2016
comment
То же самое, но другое: youtube.com/watch?v=q6zF3vf114M с devoxx 2017. - person Sormuras; 17.04.2017

(Не)изменчивость

Прежде всего, важно отметить, что фабрики коллекций возвращают неизменяемые варианты. К сожалению, это не отображается в системе типов, поэтому вам нужно отслеживать это вручную/мысленно. Это уже запрещает некоторые замены, которые в противном случае могли бы быть полезными, поэтому он должен стать 0. в вашем списке правил. :)

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

private final Set<String> commonLetters = initialCommonLetters()

private static Set<String> initialCommonLetters() {
    Set<String> letters = new HashSet<>();
    letters.add("a");
    letters.add("e");
    return letters;
}

Было бы здорово просто написать commonLetters = Set.of("a", "e");, но это, скорее всего, сломает другой код, поскольку возвращаемый набор неизменен.

Константы

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

Замена

Как вы сказали, похоже, нет причин заменять вызовы Collections::empty..., Collections::singleton... или Arrays::asList просто ради удовольствия. Что бы я сделал, однако, как только я начну использовать новые методы в классе, я бы также заменил старые варианты, чтобы код полагался на меньшее количество концепций, облегчая его понимание.

предпочтение

Последний аргумент также может применяться к вариантам of() в целом. В то время как Collections::empty... и Collections::singleton... несколько яснее объясняют свое намерение, я немного склоняюсь к тому, что постоянное использование of, независимо от того, сколько аргументов у вас есть, компенсирует это преимущество за счет написания кода, который в целом использует меньше понятий.

Не вижу причин продолжать использовать Arrays::asList.

person Nicolai Parlog    schedule 30.11.2016
comment
Collections::singletonList тоже умер? - person Sormuras; 30.11.2016
comment
Только что включил. - person Nicolai Parlog; 30.11.2016
comment
@Sormuras, возможно, вы захотите принять ответ Стюарта Маркса, так как он содержит гораздо больше деталей, чем мой. - person Nicolai Parlog; 03.12.2016