Java Convenience Factory Methods for Collections – Java集合的方便工厂方法

最后修改: 2017年 2月 1日

1. Overview

1.概述

Java 9 brings the long-awaited syntactic sugar for creating small unmodifiable Collection instances using a concise code one-liner. As per JEP 269, new convenience factory methods will be included in JDK 9.

Java 9 带来了期待已久的语法糖,用于使用简明的代码单线程创建小型的不可修改的Collection实例。根据JEP 269,JDK 9将包含新的便利工厂方法。

In this article, we’ll cover its usage along with the implementation details.

在这篇文章中,我们将介绍它的用法和实现细节。

2. History and Motivation

2.历史和动机

Creating a small immutable Collection in Java is very verbose using the traditional way.

在Java中创建一个小的不可变的Collection,用传统的方法是非常冗长的。

Let’s take an example of a Set:

让我们举一个Set的例子。

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

That’s way too much code for a simple task and it should be possible to be done in a single expression.

对于一个简单的任务来说,那是太多的代码了,它应该可以在一个表达式中完成。

The above is also true for a Map.

上述情况对于Map.也是如此。

However, for List, there’s a factory method:

然而,对于List,有一个工厂方法。

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

Although this List creation is better than the constructor initialization, this is less obvious as the common intuition would not be to look into Arrays class for methods to create a List:

尽管这种List创建比构造函数初始化更好,但这是不明显的,因为一般的直觉不会去研究Arrays类中的方法来创建List

There are other ways of reducing verbosity like the double-brace initialization technique:

还有其他的方法可以减少冗长的语言,比如双括号初始化技术。

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

or by using Java 8 Streams:

或通过使用Java 8 Streams

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

The double brace technique is only a little less verbose but greatly reduces the readability (and is considered an anti-pattern).

双大括号技术只是少了一点啰嗦,但大大降低了可读性(被认为是一种反模式)。

The Java 8 version, though, is a one-line expression, and it has some problems, too. First, it’s not obvious and intuitive. Second, it’s still verbose. Third, it involves the creation of unnecessary objects. And fourth, this method can’t be used for creating a Map.

不过,Java 8的版本是一个单行表达式,它也有一些问题。首先,它不是很明显和直观的。第二,它仍然很冗长。第三,它涉及到创建不必要的对象。第四,这个方法不能用于创建Map

To summarize the shortcomings, none of the above approaches treat the specific use case creating a small unmodifiable Collection first-class class problem.

总结一下缺点,上述方法都没有处理创建一个小型的不可修改的Collection 第一类的具体用例问题。

3. Description and Usage

3.描述和使用

Static methods have been provided for List, Set, and Map interfaces which take the elements as arguments and return an instance of List, Set, and Map, respectively.

我们为ListSetMap接口提供了静态方法,它们将元素作为参数,并分别返回ListSetMap的实例。

This method is named of(…) for all the three interfaces.

这个方法在所有三个接口中都被命名为of(…)

3.1. List and Set

3.1.ListSet

The signature and characteristics of List and Set factory methods are the same:

ListSet工厂方法的签名和特征是相同的。

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

usage of the methods:

方法的使用。

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

As we can see, it’s very simple, short, and concise.

正如我们所看到的,它非常简单、短小、简明。

In the example, we’ve used the method with takes exactly three elements as parameters and returns a List / Set of size 3.

在这个例子中,我们使用的方法是以三个元素作为参数并返回大小为3的List / Set

But, there are 12 overloaded versions of this method – eleven with 0 to 10 parameters and one with var-args:

但是,这个方法有12个重载版本–11个带0到10个参数,一个带var-args。

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

static <E> List<E> of(E... elems)

For most practical purposes, 10 elements would be sufficient, but if more are required, the var-args version can be used.

对于大多数实用的目的,10个元素就足够了,但如果需要更多,可以使用var-args版本。

Now, we may ask, what is the point of having 11 extra methods if there’s a var-args version that can work for any number of elements.

现在,我们可能会问,如果有一个var-args版本可以适用于任何数量的元素,那么拥有11个额外的方法有什么意义呢?

The answer to that is performance. Every var-args method call implicitly creates an array. Having the overloaded methods avoid unnecessary object creation and the garbage collection overhead thereof. On the contrary, Arrays.asList always creates that implicit array and, consequently, is less efficient when the number of elements is low.

这个问题的答案是性能。每个var-args方法的调用都会隐含地创建一个数组。相反,Arrays.asList总是创建隐式数组,因此,当元素的数量较少时,效率较低。

During the creation of a Set using a factory method, if duplicate elements are passed as parameters, then IllegalArgumentException is thrown at runtime:

在使用工厂方法创建Set期间,如果重复的元素被作为参数传递,那么在运行时将抛出IllegalArgumentException

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

An important point to note here is that since the factory methods use generics, primitive types get autoboxed.

这里需要注意的一点是,由于工厂方法使用了泛型,原始类型会被自动勾选。

If an array of primitive type is passed, a List of array of that primitive type is returned.

如果传递了一个原始类型的数组,就会返回该原始类型的List array

For example:

比如说。

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

In this case, a List<int[]> of size 1 is returned and the element at index 0 contains the array.

在这种情况下,一个大小为1的List<int[]>被返回,索引为0的元素包含数组。

3.2. Map

3.2. 地图

The signature of Map factory method is:

Map工厂方法的签名是。

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

and the usage:

和用法。

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

Similarly to List and Set, the of(…) method is overloaded to have 0 to 10 key-value pairs.

ListSet类似,of(…)方法被重载以拥有0到10个键值对。

In the case of Map, there is a different method for more than 10 key-value pairs:

Map的情况下,对于超过10个键值对有不同的方法。

static <K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries)

and it’s usage:

以及它的用途。

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

Passing in duplicate values for Key would throw an IllegalArgumentException:

为Key传入重复的值会抛出一个IllegalArgumentException

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

Again, in the case of Map too, the primitive types are autoboxed.

同样,在Map的情况下,原始类型也是自动的。

4. Implementation Notes

4.实施说明

The collections created using the factory methods are not commonly used implementations.

使用工厂方法创建的集合并不是常用的实现方式。

For example, the List is not an ArrayList and the Map is not a HashMap. Those are different implementations that are introduced in Java 9. These implementations are internal and their constructors have restricted access.

例如,List不是ArrayListMap不是HashMap。这些都是Java 9中引入的不同的实现。 这些实现是内部的,它们的构造函数有限制的访问。

In this section, we’ll see some important implementation differences which are common to all three types of collections.

在这一节中,我们将看到一些重要的实现差异,这些差异是所有三种类型的集合所共有的。

4.1. Immutable

4.1.不变的

The collections created using factory methods are immutable, and changing an element, adding new elements, or removing an element throws UnsupportedOperationException:

使用工厂方法创建的集合是不可改变的,改变元素、添加新元素或删除元素会抛出UnsupportedOperationException

@Test(expected = UnsupportedOperationException.class)
public void onElemAdd_ifUnSupportedOpExpnThrown_thenSuccess() {
    Set<String> set = Set.of("foo", "bar");
    set.add("baz");
}
@Test(expected = UnsupportedOperationException.class)
public void onElemModify_ifUnSupportedOpExpnThrown_thenSuccess() {
    List<String> list = List.of("foo", "bar");
    list.set(0, "baz");
}
@Test(expected = UnsupportedOperationException.class)
public void onElemRemove_ifUnSupportedOpExpnThrown_thenSuccess() {
    Map<String, String> map = Map.of("foo", "a", "bar", "b");
    map.remove("foo");
}

4.2. No null Element Allowed

4.2.不允许有null元素

In the case of List and Set, no elements can be null. In the case of a Map, neither keys nor values can be null. Passing null argument throws a NullPointerException:

ListSet的情况下,没有元素可以是null。如果是Map,键和值都不能是null。传递null参数会抛出NullPointerException

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

As opposed to List.of, the Arrays.asList method accepts null values.

List.of相反,Arrays.asList方法接受null值。

4.3. Value-Based Instances

4.3.基于价值的实例

The instances created by factory methods are value-based. This means that factories are free to create a new instance or return an existing instance.

工厂方法所创建的实例是基于值的。这意味着工厂可以自由地创建一个新的实例或返回一个现有的实例。

Hence, if we create Lists with same values, they may or may not refer to the same object on the heap:

因此,如果我们用相同的值创建Lists,它们可能指的是堆上的同一个对象,也可能不是。

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

In this case, list1 == list2 may or may not evaluate to true depending on the JVM.

在这种情况下,list1 == list2可能会也可能不会评估为true,这取决于JVM。

4.4. Serialization

4.4.序列化

Collections created from factory methods are Serializable if the elements of the collection are Serializable.

如果集合中的元素是可序列化的,那么由工厂方法创建的集合就是可序列化的

5. Conclusion

5.结论

In this article, we introduced the new factory methods for Collections introduced in Java 9.

在这篇文章中,我们介绍了Java 9中引入的集合的新工厂方法。

We concluded why this feature is a welcome change by going over some past methods for creating unmodifiable collections. We covered it’s usage and highlighted key points to be considered while using them.

我们通过回顾过去一些创建不可修改的集合的方法,总结了为什么这个功能是一个受欢迎的变化。我们涵盖了它的用法,并强调了在使用它们时需要考虑的关键点。

Finally, we clarified that these collections are different from the commonly used implementations and pointed out key differences.

最后,我们澄清了这些集合与常用的实现方式不同,并指出了关键的区别。

The complete source code and unit tests for this article are available over on GitHub.

本文的完整源代码和单元测试可在GitHub上获得