Методы фабрики удобства 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 extends K,? extends V>... 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.