平台之所以谓之平台,以其能建立一个生态,并与之外围达成共赢。霸道点的平台也会反噬外围生态,像微软集成浏览器,媒体播放器。还有即将的 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 后有创建不可变集合的需求,只要用三大接口 List
, Set
,Map
中的 of(...)
方法就对了。
Java 9 之前,当我们需要集合相关的操作,两个选择:
- 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) //创建不可变集合 - 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) 为例:
1 2 3 4 5 6 |
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(...)
方法的样式就是
1 2 3 4 |
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 是
12 def list = ['Groovy', 'Java', 'Scala'].asImmutable()def map = [key1: 'value1', key2: 'value2'].asImmutable()
Scala 借助于伴生类和 Apply 方法就更简洁了
123 val list = List("Groovy", "Java", "Scala")val set = Set("A", "B", "C")val map = Map("key1" -> "value1", "key2" -> "value2")
其实 JDK 是否包含流行的第三方组件库也是 Java 社区人民的呼声,不能怨 JDK 或 Oracle 的,总之方便的还是开发者。
这种不可变的应用场景是什么呢?在固定条目的情况下,不申请多余的空间吗 ?
那如果条目固定,那普通的list也可以固定大小的,能带来性能上多大的提升吗?
不可变的应用场景有类似系统配置式的集合,比如可接受的输入必须是某个预设集合中的元素之一,还有配置的 Map 用于程序逻辑跳转的。
固定条目的情况下,避免了随着元素的增加进行 1.5 或 2 倍的扩容,已有元素的拷贝操作,也不至于在扩容到了原来大小 2 倍后只填充了一个元素而浪费许多空间。
感谢您经常光顾,以后来常来的话可以通过这个链接 注册为本站用户。需要邮件发送邀请码才能注册,否则有大量的恶意用户涌进来。