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) 为例:

也就是 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(...) 方法的样式就是

后话

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

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

Groovy 创建不可变  List 是

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

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

本文链接 https://yanbin.blog/java-9-quick-create-immutable-collections/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

3 Comments
Inline Feedbacks
View all comments
wayne
wayne
5 years ago

这种不可变的应用场景是什么呢?在固定条目的情况下,不申请多余的空间吗 ?
那如果条目固定,那普通的list也可以固定大小的,能带来性能上多大的提升吗?