Рубрики
Без рубрики

Методы фабрики удобства Java для коллекций

Краткое и практическое руководство по созданию коллекций и карт с использованием заводских методов Java 9.

Автор оригинала: baeldung.

1. Обзор

Java 9 приносит долгожданный синтаксический сахар для создания небольших неизменяемых экземпляров Collection с использованием краткого однострочного кода. Согласно JEP 269 , новые методы фабрики удобства будут включены в JDK 9.

В этой статье мы рассмотрим его использование вместе с деталями реализации.

2. История и мотивация

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

Давайте возьмем пример Set :

Set set = new HashSet<>();
set.add("foo");
set.add("bar");
set.add("baz");
set = Collections.unmodifiableSet(set);

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

Вышесказанное также верно для карты .

Однако для List существует фабричный метод:

List list = Arrays.asList("foo", "bar", "baz");

Хотя это Список создание лучше, чем инициализация конструктора, это менее очевидно , поскольку общая интуиция не будет заглядывать в Массивы класс для методов создания списка :

Существуют и другие способы уменьшения многословия, такие как инициализация двойных скобок техника:

Set set = Collections.unmodifiableSet(new HashSet() {{
    add("foo"); add("bar"); add("baz");
}});

или с помощью Java 8 Streams :

Stream.of("foo", "bar", "baz")
  .collect(collectingAndThen(toSet(), Collections::unmodifiableSet));

Техника двойной скобки лишь немного менее многословна, но значительно снижает читабельность (и считается анти-шаблоном).

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

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

3. Описание и использование

Статические методы были предоставлены для интерфейсов List , Set и Map , которые принимают элементы в качестве аргументов и возвращают экземпляр List , Set и Map соответственно.

Этот метод называется of(…) для всех трех интерфейсов.

3.1. Список и набор

Подпись и характеристики методов List и Set factory одинаковы:

static  List of(E e1, E e2, E e3)
static  Set  of(E e1, E e2, E e3)

использование методов:

List list = List.of("foo", "bar", "baz");
Set set = Set.of("foo", "bar", "baz");

Как мы видим, это очень просто, коротко и лаконично.

В примере мы использовали метод, который принимает ровно три элемента в качестве параметров и возвращает List |/Set размера 3.

Но существует 12 перегруженных версий этого метода – одиннадцать с параметрами от 0 до 10 и одна с var-args:

static  List of()
static  List of(E e1)
static  List of(E e1, E e2)
// ....and so on

static  List of(E... elems)

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

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

Ответ на этот вопрос-производительность. Каждый вызов метода var-args неявно создает массив. Наличие перегруженных методов позволяет избежать ненужного создания объектов и связанных с этим накладных расходов на сборку мусора. Напротив, Arrays.asList всегда создает этот неявный массив и, следовательно, менее эффективен, когда количество элементов невелико.

При создании набора с использованием заводского метода, если в качестве параметров передаются повторяющиеся элементы, то IllegalArgumentException выбрасывается во время выполнения:

@Test(expected = IllegalArgumentException.class)
public void onDuplicateElem_IfIllegalArgExp_thenSuccess() {
    Set.of("foo", "bar", "baz", "foo");
}

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

Если передается массив примитивного типа, возвращается Список из массива этого примитивного типа.

Например:

int[] arr = { 1, 2, 3, 4, 5 };
List list = List.of(arr);

В этом случае возвращается List размера 1, а элемент с индексом 0 содержит массив.

3.2. Карта

Подпись метода Map factory:

static  Map of(K k1, V v1, K k2, V v2, K k3, V v3)

и использование:

Map map = Map.of("foo", "a", "bar", "b", "baz", "c");

Аналогично List и Set , метод of(…) перегружен, чтобы иметь от 0 до 10 пар ключ-значение.

В случае Map существует другой метод для более чем 10 пар ключ-значение:

static  Map ofEntries(Map.Entry... entries)

и это использование:

Map map = Map.ofEntries(
  new AbstractMap.SimpleEntry<>("foo", "a"),
  new AbstractMap.SimpleEntry<>("bar", "b"),
  new AbstractMap.SimpleEntry<>("baz", "c"));

Передача повторяющихся значений для ключа вызовет исключение IllegalArgumentException :

@Test(expected = IllegalArgumentException.class)
public void givenDuplicateKeys_ifIllegalArgExp_thenSuccess() {
    Map.of("foo", "a", "foo", "b");
}

Опять же, в случае Map to примитивные типы автобоксов.

4. Примечания по осуществлению

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

Например, List не является ArrayList , а Map не является HashMap . Это разные реализации, которые представлены в Java 9. Эти реализации являются внутренними, и их конструкторы имеют ограниченный доступ.

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

4.1. Неизменяемость

Коллекции, созданные с использованием заводских методов, являются неизменяемыми, и изменение элемента, добавление новых элементов или удаление элемента вызывает Исключение UnsupportedOperationException :

@Test(expected = UnsupportedOperationException.class)
public void onElemAdd_ifUnSupportedOpExpnThrown_thenSuccess() {
    Set set = Set.of("foo", "bar");
    set.add("baz");
}
@Test(expected = UnsupportedOperationException.class)
public void onElemModify_ifUnSupportedOpExpnThrown_thenSuccess() {
    List list = List.of("foo", "bar");
    list.set(0, "baz");
}

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

@Test(expected = UnsupportedOperationException.class)
public void onElemRemove_ifUnSupportedOpExpnThrown_thenSuccess() {
    Map map = Map.of("foo", "a", "bar", "b");
    map.remove("foo");
}

4.2. Нулевой Элемент Не Допускается

В случае List и Set никакие элементы не могут быть null . В случае Map ни ключи , ни значения не могут быть null . Передача null аргумента вызывает исключение |/NullPointerException :

@Test(expected = NullPointerException.class)
public void onNullElem_ifNullPtrExpnThrown_thenSuccess() {
    List.of("foo", "bar", null);
}

В отличие от List.of , метод Arrays.asList принимает значения null .

4.3. Экземпляры, основанные на Значениях

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

Следовательно, если мы создаем списки с одинаковыми значениями, они могут ссылаться или не ссылаться на один и тот же объект в куче:

List list1 = List.of("foo", "bar");
List list2 = List.of("foo", "bar");

В этом случае list1 может принимать или не принимать значение true в зависимости от JVM.

4.4. Сериализация

Коллекции, созданные с помощью заводских методов, Сериализуемы , если элементы коллекции Сериализуемы.

5. Заключение

В этой статье мы представили новые фабричные методы для коллекций, представленные в Java 9.

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

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

Полный исходный код и модульные тесты для этой статьи доступны на GitHub .