平台之所以谓之平台,以其能建立一个生态,并与之外围达成共赢。霸道点的平台也会反噬外围生态,像微软集成浏览器,媒体播放器。还有即将的 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 之前,当我们需要集合相关的操作,两个选择:
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) //创建不可变集合
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 异常 接口 List 的静态 of(...) 方法有
注意:不允许包含 null 元素,否则抛出 NullPointerException 异常
注意:不允许包含 null 元素,否则抛出 NullPointerException 异常。并且不能有重复元素(调用方法时保证),否则抛出 IllegalArgumentException 异常。不像 HashSet 会帮我们去重。
注间: 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 的,总之方便的还是开发者。