转载

Java 9 - 快速创建不可变集合

平台之所以谓之平台,以其能建立一个生态,并与之外围达成共赢。霸道点的平台也会反噬外围生态,像微软集成浏览器,媒体播放器。还有即将的 iOS 12 要把应用商店多是收费的 AR 皮尺放到它自己系统中来,走别人的路,让别人无路可走。从此众泰皮尺部的唯一的生产工具就会是人手一部能安装 iOS 12 iPhone 了。

JDK 也不例外,Java 8 之前日期库的话 Joda-Time 是首要之选,Java 8 集成后应该是鲜有人问津。以往说到集合操作库,有两个选择,其一为 Apache Commons Collections ,二为 Google 的 Guava ,当然前者与后者竞争中也早已败下阵来,况且前者还受到 Java 8 的夹击。而本文要说的可以说是 Java 9 把 Guava 中创建不可变集合的方式据为已用了,直截了当的说,凡是 Java 9 后有创建不可变集合的需求,只要用三大接口 ListSetMap 中的 of(...) 方法就对了。

Java 9 之前,当我们需要集合相关的操作,两个选择:

  1. Apache Commons Collections 的几个类 ListUtils , SetUtils , MapUtils , 和 CollectionsUtils 。比如它们提供的以下几些个工具方法

    ListUtils.unmodifiableList<List<? extends E> list)   //创建不可变 List

    SetUtils.emptySet()  //不可变的空  Set

    SetUtils.unmodifiableSet(Set<? extends E> set)  //创建不可变 Set

    MapUtils.unmodifiableMap(Map<? extends K, ? extends V> map)  //创建不可变 Map

    CollectionUtils.unmodifiableCollection(Collection<? extends C> collection)  //创建不可变集合

  2. Guava 的几个类 ImmutableList , ImmutableSet , 和 ImmutableMap 。而它们创建不可变集合的方式就是通过各自的 of(...)  方法,以 ImmutableList  为例(其余两个类也类似),它有
    of(): ImmutableList<E>  of(E element): ImmutableList<E>  of(E e1, E e2): ImmutableList<E>  of(E e1, E e2, E e3): ImmutableList<E>  ......  of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10, E e11, E e12, E... others): ImmutableList<E>
    

而我们今天要说的 Java 9 在接口 List, Set, Map 上增加的方法就是偷师于 Guava 的以上三个类,也是提供的一堆的 of(...) 方法来创建对应的不可变集合,不过还是略有增强。

既然是读的 《Java 9 Revealed》这本书的内容,也看下 Java 9 之前 JDK API Collections 可以怎么创建不可变集合,相关方法:

emptyList(): List<T>  emptySet(): Set<T>  emptyMap(): Map<K, V>
singletonList<T o): List<T>  singleton(T o): Set<T>  singletonMap(K key, V value): Map<K, V)
unmodifiableList(List<? extends T> list): List<T>  unmodifiableSet(Set<? extends T> s): Set<T>  unmodifiableMap(Map<? extends K, ? extends V> m): Map<K, V)

上面最后三个方法可以看到要创建不可变的 List, Set, 或 Map,需要对一个现有的集合进行包装,这种操作就有些不那么纯洁了,因为对原始集合的修改会影响到所谓的不可变集合。以 unmodifiableList(List<? extends T> list) 为例:

List<String> list = new ArrayList<>();
list.add("a");
List<String> immutableList = Collections.unmodifiableList(list);
//immutableList.add("hello"); //虽然不能这样做
list.add("b"); //但修改原始集合让人质疑 immutableList 的不可变性
System.out.println(immutableList);  //[a, b]

也就是 unmodifiableXxx(...) 产生的不可变集合实际上是有缺陷的。

而书中未提 Arrays.asList(T... a) 方法是由于它创建的是一个可变的 ArrayList<T> 实例,不在此讨论之列。

下面快速过一下 Java 9 的 List,Set,Map 的 of(...) 静态方法。先说一下那些 of(...) 及得到的结果的共同特点

  • 每个接口都提供了有限参数和不定参数的 of(...)  方法,有限参数的 of(...)  方法是为了性能考虑(如避免了参数装箱为数组)
  • 不同的 of(...)  方法返回的内部实例类型也是不确定的,可以查看每一个 of(...)  方法的返回类型
  • of(...)  返回的实例都是可序列化的,所以只要保证其中的每一个元素(Map 则包括  key 和  value )是可序列化的,那么集体本身就可被序列化
  • 所有的 of(...)  方法返回的都是真正的不可变集合,尝试对它们的任何修改都会抛出 UnsupportedOperationException 异常
  • 元素或元素的组成部分(Map 的 key 和 value) 都不允许 null 值的出现,否则抛出 NullPointerException 异常

List.of(...) 创建不可变的 List

接口 List 的静态 of(...) 方法有

  • static <E> List<E> of()   创建空列表,返回 ImmutableCollections.List0.instance()
  • static <E> List<E> of(E e1)    返回 new ImmutableCollections.List1<>(e1)
  • static <E> List<E> of(E e1, E e2) 返回 new ImmutalbeCollection.List2<>(e1, e2)
  • ......
  • static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E 10)
  • static <E> List<E> of(E... elements)

注意:不允许包含 null 元素,否则抛出 NullPointerException 异常

Set.of(...) 创建不可变的 Set

  • static <E> Set<E> of()   创建空 set
  • static <E> Set<E> of(E e1) 
  • static <E> Set<E> of(E e1, E e2)
  • ......
  • static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E 10)
  • static <E> Set<E> of(E... elements)

注意:不允许包含 null 元素,否则抛出 NullPointerException 异常。并且不能有重复元素(调用方法时保证),否则抛出 IllegalArgumentException 异常。不像  HashSet 会帮我们去重。

Map.of(...) 创建不可变的  Map

  • static <K, V> Map<K, V> of()  创建空 Map
  • static <K, V> Map<K, V> of(K k1, V v1)
  • static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2)
  • ......
  • 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)
  • static <K, V> Map<K, V> ofEntries(Map.Entry<? extends K, ? extends V>... entries) 

注间: of(...) 方法与  Guava 的 ImmutableMap 的用法是一样的,并且也是 key, value 都不允许有 null 值,否则抛出 NullPointerException 异常。 of(...) 方法的参数对偶出现,key 和 value 交替。对于不定元素个数无法用 of(...) 方法是因为 Java 只支持最后一个元素的可变,所以只能把 key/value 封装起来,引入上面的最后一个方法

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

再静态引入 Map.entry 方法后,我们使用 ofEntries(...) 方法的样式就是

import static java.util.Map.entry;
 
Map<Integer, String> numberToWord = Map.ofEntries(
    entry(1, "One"), entry(2, "Two"), entry(3, "Three"));

后话

Java 9 引入上述方法来创建不可变集合能够更有效使用内存,因为在集合的创建时元素的个数是确定的,不需要进行内部存储的动态伸展。

Java 9 使用 List,Set 和 Map 的 of(...) 方法来创建不可变已经是很大的进步了,当然不能与动态语言或 Scala 相比,看

Groovy 创建不可变  List 是

def list = ['Groovy', 'Java', 'Scala'].asImmutable()
def map = [key1: 'value1', key2: 'value2'].asImmutable()

Scala 借助于伴生类和 Apply 方法就更简洁了

val list = List("Groovy", "Java", "Scala")
val set = Set("A", "B", "C")
val map = Map("key1" -> "value1", "key2" -> "value2")

其实 JDK 是否包含流行的第三方组件库也是 Java 社区人民的呼声,不能怨 JDK 或 Oracle 的,总之方便的还是开发者。

原文  https://unmi.cc/java-9-quick-create-immutable-collections/
正文到此结束
Loading...