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

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

1. обзор

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

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

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

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

Возьмем примерSet:

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

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

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

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

Хотя это созданиеList лучше, чем инициализация конструктора, этоless obvious, поскольку общая интуиция не заключается в том, чтобы искать в классеArrays методы для созданияList:

Есть и другие способы уменьшить многословие, такие как методdouble-brace:

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

или используя Java 8Streams:

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

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

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

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

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

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

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

3.1. List иSet

Сигнатура и характеристики фабричных методовList иSet одинаковы:

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

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

В этом примере мы использовали метод with, который принимает в качестве параметров ровно три элемента и возвращает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 элементов, но если потребуется больше, можно использовать версию var-args.

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

Ответ на это производительность. Every var-args method call implicitly creates an array. Having the overloaded methods avoid unnecessary object creation and the garbage collection overhead thereof.с

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

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

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

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

Например:

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

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

3.2. Mapс

Сигнатура фабричного методаMap:

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

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

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

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

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");
}
@Test(expected = UnsupportedOperationException.class)
public void onElemRemove_ifUnSupportedOpExpnThrown_thenSuccess() {
    Map map = Map.of("foo", "a", "bar", "b");
    map.remove("foo");
}

4.2. Элементnull не разрешен

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

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

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

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

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

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

В этом случаеlist1 == list2 может или не может оценивать какtrue, в зависимости от JVM.

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

Коллекции, созданные из фабричных методов, являютсяSerializable, если элементы коллекции -Serializable.

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

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

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

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

Полный исходный код этой статьи и модульных тестов -available over on GitHub.