Java9中集合的便利工厂方法


Java9没有像它的前身那样给我们的编码方式带来太多戏剧性的变化,但是我们肯定会有一些奇特的特性--我想在本文中向您介绍其中的一个特性。大约5年前,当我开始在Groovy中编码时,我对那里的集合支持感到惊讶--尤其是在初始化一个不可变列表时。我可以用一句简单的话来做:

def band = ["Bruce", "Steve", "Adrian", "Dave", "Janic", "Nicko"].asImmutable()


今年,我也开始用Scala编码,这里我们有一些花哨的技巧,比如:

val band = "Bruce" :: "Steve" :: "Adrian" :: "Dave" :: "Janick" :: "Nicko" :: Nil

或者干脆

val band = List("Bruce", "Steve", "Adrian", "Dave", "Janick", "Nicko")


与此同时,Java似乎在用一个add语句列表折磨我:

List<String> band = new ArrayList<>();
band.add("Bruce");
band.add("Steve");
band.add("Adrian");
band.add("Janick");
band.add("Nicko");
band = Collections.unmodifiableList(band);


或者所谓的“双括号”初始化(实际上这并不是任何特殊的Java特性,而是从匿名类和初始化块中获益的一种变通方法):

List<String> band = Collections.unmodifiableList(new ArrayList<>() {
    {
        add("Bruce");
        add("Steve");
        add("Adrian");
        add("Janick");
        add("Nicko");
    }
});


或使用Arrays API将数组转换为ArrayList

List<String> band = Collections
  .unmodifiableList(Arrays.asList("Bruce","Steve","Adrian", 
                                  "Dave", "Janick","Nicko"));


最近,我还可以从Stream API

List<String> band = Collections
  .unmodifiableList(Stream.of("Bruce","Steve","Adrian", "Dave", "Janick","Nicko")
    .collect(toList()));


最后两个选项是最可爱的,但是为什么我需要首先创建数组或流来创建列表,为什么我不能只使用Collections API而不是Arrays API或者Stream API?好吧,我甚至都不想回忆Sets或者Maps在爪哇--想起它让我半夜惊出一身冷汗。幸运的是,Java9的作者实现了JEP 269: Convenience Factory Methods for Collections,它只是提供了一组支持创建不可变集合实例的静态工厂方法。救世主!继续阅读以获得此功能的概述。

不可变集合

在我们深入讨论这个主题之前,您应该知道不变集合到底是什么。它们是一旦创建就不能修改的集合。我所说的“修改”是指它们的状态(它们持有的引用,保留这些引用的顺序以及元素的数量)将保持不变。请注意,如果一个不可变集合保存了可变对象,那么它们的状态无论如何都不会受到保护。虽然不可变集合仍然实现List,Set,或Map接口,修改其内容的方法引发UnsupportedOperationException您将在后面的小节中找到一组这样的方法。

实施

实施可以分为两个主要部分。首先,java.util包用package-private ImmutableCollections类进行了充实,其中包含提供不可变性特性的类。其次,这些类的实例是在已经存在的接口中的静态工厂方法的帮助下创建的。List,Set,和Map在下面的部分中,您将找到对每个集合接口的两组功能的描述。

列表

不可变列表有一个抽象基类AbstractImmutableList<E>和四种实现方式:

  •  List0<E>  
  •  List1<E>  
  •  List2<E>  
  •  ListN<E>  

这些类型中的每一个都对应于用于创建它们的元素的数量。在java.util.List 接口,我们有12个静态工厂方法,它们使用上述实现来创建不可变对象:

// creates empty immutable list
static <E> List<E> of()

// creates one-element immutable list
static <E> List<E> of(E e1)

// creates two-element immutable list
static <E> List<E> of(E e1, E e2)

...

// creates ten-element immutable list
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10)

// creates N-element immutable list
static <E> List<E> of(E... elements)


抛出的方法UnsupportedOperationException

boolean add(E e);
boolean addAll(Collection<? extends E> c);
boolean addAll(int index, Collection<? extends E> c);
void    clear();
boolean remove(Object o);
boolean removeAll(Collection<?> c);
boolean removeIf(Predicate<? super E> filter);
void    replaceAll(UnaryOperator<E> operator);
boolean retainAll(Collection<?> c);
void    sort(Comparator<? super E> c);


除了保护列表的内容之外,我们还获得了一个验证,该验证防止我们使用null价值。尝试运行以下代码段将以NullPointerException结束:

// throws NullPointerException
List<String> band = List.of("Bruce","Steve","Adrian", "Dave", "Janick", null);


现在,这里有一个如何正确创建不可变列表的示例:

List<String> band = List.of("Bruce","Steve","Adrian", "Dave", "Janick","Nicko");


设置

不可变集的实现方式与我们看到的List接口。它有一个抽象基类AbstractImmutableSet<E>和四种实现方式:

  •  Set0<E>  
  •  Set1<E>  
  •  Set2<E>  
  •  SetN<E>  

这也与创建它们所使用的元素的数量相对应。在java.util.Set接口,我们有12个静态工厂方法:

// creates empty immutable set
static <E> Set<E> of()

// creates one-element immutable set
static <E> Set<E> of(E e1)

// creates two-element immutable set
static <E> Set<E> of(E e1, E e2)

...

// creates ten-element immutable set
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10)

// creates N-element immutable set
static <E> Set<E> of(E... elements)


抛出的方法UnsupportedOperationException 

boolean add(E e)
boolean addAll(Collection<? extends E> c)
void    clear()
boolean remove(Object o)
boolean removeAll(Collection<?> c)
boolean removeIf(Predicate<? super E> filter)
boolean retainAll(Collection<?> c)


与不可变列表一样,我们不能实例化Set具有空值:

// throws NullPointerException
Set<String> band = Set.of("Bruce","Steve","Adrian", "Dave", "Janick", null);


您还应该知道,集合与列表在某种程度上是不同的--它们不能有重复的值。使用新提供的工厂方法,我们将无法初始化一个不可变的Set传递多个相同值的对象-我们将得到IllegalArgumentException

// throws IllegalArgumentException
Set<String> guitarists = Set.of("Adrian", "Dave", "Janick", "Janick");


现在,这里有一个如何正确创建不可变集的示例:

Set<String> band = Set.of("Bruce","Steve","Adrian", "Dave", "Janick","Nicko");


地图

在我们描述不可变映射的技术细节之前,我们应该从条目(java.util.Map.Entry接口)-键值对的聚合。从Java9中,我们有另一个条目的包私有实现-java.util.KeyValueHolder-一个不可变的容器,它防止实例化具有等于null的键或值的条目(引发NullPointerException如果这样做)。

为了创建一个不可变的条目,我们可以从java.util.Map接口:

static <K, V> Entry<K, V> entry(K k, V v)


不可变映射有一个抽象基类,AbstractImmutableMap<K, V>  ,有三种实现方式:

  •  Map0<K, V>
  •  Map1<K, V>
  •  MapN<K, V>  

java.util.Map接口:

// creates an empty map
static <K, V> Map<K, V> of()

// creates one-element map
static <K, V> Map<K, V> of(K k1, V v1)

// creates two-element map
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2)

...

// creates ten-element map
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4,
                           K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8, 
                           K k9, V v9, K k10, V v10)

// creates N-element map
static <K, V> Map<K, V> ofEntries(Entry<? extends K, ? extends V>... entries)


你可以看到它不同于List或者Set工厂方法。使用of方法,我们可以创建最多包含10个元素的不可变映射。如果我们想要一个更大的,我们需要使用ofEntries方法,接受varargsEntity。这并不奇怪,因为我们可以用varargs一个方法中只有一个参数,因此我们无法通过这种方式传递不同类型的键和值。

与列表和集合一样,我们有一些方法抛出UnsupportedOperationException

void clear()
V compute(K key, BiFunction<? super K,? super V,? extends V> rf)
V computeIfAbsent(K key, Function<? super K,? extends V> mf)
V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> rf)
V merge(K key, V value, BiFunction<? super V,? super V,? extends V> rf)
V put(K key, V value)
void putAll(Map<? extends K,? extends V> m)
V putIfAbsent(K key, V value)
V remove(Object key)
boolean remove(Object key, Object value)
V replace(K key, V value)
boolean replace(K key, V oldValue, V newValue)
void replaceAll(BiFunction<? super K,? super V,? extends V> f)


无论我们以何种方式创建不可变映射,我们都无法使用等于的键,值或整个条目来实例化它null下面是将引发NullPointerExceptions

// throws NullPointerExcepton because of null key
Map<String, Long> age = Map.of(null, 59L, "Steve", 61L);
// throws NullPointerExcepton because of null value
Map<String, Long> age = Map.of("Bruce", null, "Steve", 61L);
// throws NullPointerExcepton because of null entry
Map<String, Long> age = Map.ofEntries(Map.entry("Bruce", 59L), null);


与不可变集类似,我们不能创建具有重复值的映射。尝试这样做将会导致IllegalArgumentException

Map<String, Long> age = Map.of("Bruce", 59L, "Bruce", 59L);
Map<String, Long> age = Map.ofEntries(Map.entry("Bruce", 59L),
                                      Map.entry("Bruce", 59L));


下面是我们如何在Java9中正确创建不可变映射的一些示例:

Map<String, Long> age = Map.of("Bruce", 59L, "Steve", 61L, "Dave", 60L,
                               "Adrian", 60L, "Janick", 60L, "Nicko", 65L);
Map<String, Long> age = Map.ofEntries(Map.entry("Bruce", 59L),
                                      Map.entry("Steve", 61L),
                                      Map.entry("Dave", 60L),
                                      Map.entry("Adrian", 60L),
                                      Map.entry("Janick", 60L),
                                      Map.entry("Nicko", 65L));


结论

自从Java9到来以来,创建不可变集合变得非常方便。我们对每个接口都有一组静态工厂方法,这些方法除了创建不可变对象外,还防止我们插入空值或重复值(在SetMap)。我们在创建时会收到任何问题的警告,并且我们在遍历集合元素时不会得到NullPointerException.这是一个很小的特性,但肯定是一个有用的特性!



如果您喜欢这篇文章,并想了解更多关于Java集合的信息,请查看this collection of tutorials and articles所有东西上的Java集合。