[Java] JDK 9 新变化之 Convenience Factory Methods for Collections

JDK 9 新变化之 Convenience Factory Methods for Collections

背景

以下方法都是 JDK 9 中新增的。

  • List.of(...)(这里用 ... 表示我们不关心参数的类型/数量,下同)
  • Set.of(...)
  • Map.of(...)
  • Map.ofEntries(...)

详细的描述可以参考 JEP 269: Convenience Factory Methods for Collections(本文的很多内容都参考了它)。在本文中,我会在 JEP 269: Convenience Factory Methods for Collections 的基础上,谈谈自己的理解。

要点

以下方法都是 JDK 9 中新增的。有了这些方法,我们可以方便地创建包含少量元素(或者 key-value pair)的 List/Set/Map。这些方法调用起来很直观,而且它们底层的结构也比较简单。

  • List.of(...)
  • Set.of(...)
  • Map.of(...)
  • Map.ofEntries(...)

List.of(...) 的思维导图如下 ⬇️

mindmap Root("List.of(...)") List12 node11("参数中的元素数是1或2时,<br/>List.of(...) 返回 List12 的实例") node12("List12 中用 e0, e1 两个字段<br/>来保存所有的元素") ListN node21("参数中的元素数是0或大于等于3时,<br/>List.of(...) 返回 ListN 的实例") ListN 使用 elements 字段来保存所有的元素

Set.of(...) 的思维导图如下 ⬇️

mindmap Root("Set.of(...)") Set12 node11("参数中的元素数是1或2时,<br/>Set.of(...) 返回 Set12 的实例") node12("Set12 使用 e0, e1 两个字段<br/>来保存所有的元素") SetN node21("参数中的元素数是0或大于等于3时,<br/>Set.of(...) 返回 SetN 的实例") node22("SetN 使用 elements 字段来保存所有的元素") node23("SetN 使用 开放寻址法(open addressing)<br/>来访问元素")

Map.of(...)/Map.ofEntries(...) 的思维导图如下 ⬇️

mindmap Root("Map.of(...)/Map.ofEntries(...)") Map1 node11("待处理的 key-value pair 的个数是1时,<br/>Map.of(...)/Map.ofEntries(...) 返回 Map1 的实例") node12("Map1 使用 k0, v0 两个字段<br/>来保存仅有的 key-value pair") MapN node21("待处理的 key-value pair 的个数不等于1时,<br/>Map.of(...)/Map.ofEntries(...) 返回 MapN 的实例") node22("MapN 使用 table 字段来保存所有的 key 和 value") node23("MapN 使用 开放寻址法(open addressing)<br/>来访问 key-value pair")

正文

过去的做法

JDK 9 之前,如果我们想创建一个只包含 "a", "b", "c" 3 个元素的 set,可能会采用以下几种做法(这几种做法的代码均来自 JEP 269: Convenience Factory Methods for Collections)。

做法一
java 复制代码
Set<String> set = new HashSet<>(); 
set.add("a"); 
set.add("b"); 
set.add("c"); 
set = Collections.unmodifiableSet(set);

如果是对静态字段赋值,需要将这样的代码写在静态初始化块里(因为这样的代码没法在一个表达式里写完)。

做法二
java 复制代码
Set<String> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a", "b", "c")));

第二种做法会额外创建 List 对象。

做法三
java 复制代码
Set<String> set = Collections.unmodifiableSet(new HashSet<String>() {{ 
    add("a"); add("b"); add("c"); 
}});

第三种做法利用了匿名内部类。

做法四
java 复制代码
Set<String> set = Collections.unmodifiableSet(Stream.of("a", "b", "c").collect(toSet()));

第四种做法利用了 Java 8 提供的 Stream API

以上四种做法都包含了不少代码,我们只是想创建一个小的 set 而已,如果可以不用写这么多代码就好了。

过去如何创建空的 List/Set/Map

java.util.Collections 类中在更早的时候已经提供了相关的方法 ⬇️

调用 java.util.Collections 类中的哪个静态方法? 得到的是哪个类的实例?
如何创建空的 List Collections.emptyList() java.util.Collections$EmptyList
如何创建空的 Set Collections.emptySet() java.util.Collections$EmptySet
如何创建空的 Map Collections.emptyMap() java.util.Collections$EmptyMap

过去如何创建单元素(或一个 key-value pair)的 List/Set/Map

java.util.Collections 类中在更早的时候已经提供了相关的方法 ⬇️

调用 java.util.Collections 类中的哪个静态方法? 得到的是哪个类的实例?
如何创建单元素的 List Collections.singletonList(T) java.util.Collections$SingletonList
如何创建单元素的 Set Collections.singleton(T) java.util.Collections$SingletonSet
如何创建只包含一个 key-value pairMap Collections.singletonMap(K, V) java.util.Collections$SingletonMap

JDK 9

如果在 List/Set/Map 中可以提供一些静态方法,让我们可以创建含有任意多个元素(或 key-value pair)的 List/Set/Map 就好了。例如对 List/Set 而言,我们希望可以写出如下的代码。

java 复制代码
List.of(a, b, c); 
Set.of(d, e, f, g);

如果 List.of(...)/Set.of(...) 系列的方法中有支持变长参数的的版本,那么我们就可以为任意数量的元素生成对应的 List/Set

Map 而言,如果可以用如下的代码来生成包含少量 key-value pairMap 的话,将会很方便。

java 复制代码
Map.of() 
Map.of(k1, v1) 
Map.of(k1, v1, k2, v2) 
Map.of(k1, v1, k2, v2, k3, v3) 
...

如果 key-value pair 的数量比较多的话,那么 Map.of(...) 将无法支持(因为 JVM 中对方法的参数个数的上限有约束)。此时,如果有如下的方法,那么也算差强人意。

java 复制代码
Map.ofEntries(Map.Entry<K,V>...)

我们可以这样调用 ⬇️

java 复制代码
Map.ofEntries( 
    Map.entry(k1, v1), 
    Map.entry(k2, v2), 
    Map.entry(k3, v3), 
    // ... entry(kn, vn));

JDK 9 中确实新增了这些方法 ⬇️

  • List.of(...)
  • Set.of(...)
  • Map.of(...)
  • Map.ofEntries(...)

List.of(...) 返回的是以下两个类的实例 ⬇️

  • java.util.ImmutableCollections$List12
  • java.util.ImmutableCollections$ListN

Set.of(...) 返回的是以下两个类的实例 ⬇️ (更多细节可以参考我写的另一篇文章:Java 浅析 Set.of(...) 方法)

  • java.util.ImmutableCollections$Set12
  • java.util.ImmutableCollections$SetN

Map.of(...)/Map.ofEntries(...) 返回的是以下两个类的实例 ⬇️ (更多细节可以参考我写的另一篇文章:Java 浅析 Map.of(...) 方法和 Map.ofEntries(...) 方法)

  • java.util.ImmutableCollections$Map1
  • java.util.ImmutableCollections$MapN

其中

  • List12/Set12/Map1 中用字段来保存元素(或者 key-value pair
    • List12 使用 e0, e1 两个字段来保存所有元素
    • Set12 使用 e0, e1 两个字段来保存所有元素
    • Map1 使用 k0, v0 两个字段来保存仅有的那个 key-value pair
  • ListN/SetN/MapN 中用数组来保存元素(或者 key-value pair
    • ListN 使用 elements 字段来保存所有元素
    • SetN 使用 elements 字段来保存所有元素,并通过 开放寻址法(open addressing) 来访问它们
    • MapN 使用 table 字段来保存所有的 keyvalue,并通过 开放寻址法(open addressing) 来访问它们

List12/ListN/Set12/SetN/Map1/MapN 的类图如下 ⬇️

请注意:以下的类/接口在类图中被忽略了

  • java.io.Serializable
classDiagram Iterable <|-- Collection Collection <|.. AbstractCollection AbstractCollection <|-- AbstractImmutableCollection Collection <|-- SequencedCollection SequencedCollection <|-- List AbstractImmutableCollection <|-- AbstractImmutableList List <|.. AbstractImmutableList RandomAccess <|.. AbstractImmutableList AbstractImmutableList <|-- List12 AbstractImmutableList <|-- ListN Collection <|-- Set AbstractImmutableCollection <|-- AbstractImmutableSet Set <|.. AbstractImmutableSet AbstractImmutableSet <|-- Set12 AbstractImmutableSet <|-- SetN Map <|.. AbstractMap AbstractMap <|-- AbstractImmutableMap AbstractImmutableMap <|-- Map1 AbstractImmutableMap <|-- MapN <<Abstract>> AbstractCollection <<Abstract>> AbstractImmutableCollection <<Abstract>> AbstractImmutableList <<Abstract>> AbstractImmutableSet <<Abstract>> AbstractMap <<Abstract>> AbstractImmutableMap <<interface>> Iterable <<interface>> Collection <<interface>> SequencedCollection <<interface>> List <<interface>> RandomAccess <<interface>> Set <<interface>> Map
在上图中的类名/接口名 Fully Qualified Name
AbstractCollection java.util.AbstractCollection
AbstractImmutableCollection java.util.ImmutableCollections$AbstractImmutableCollection
AbstractImmutableList java.util.ImmutableCollections$AbstractImmutableList
AbstractImmutableMap java.util.ImmutableCollections$AbstractImmutableMap
AbstractImmutableSet java.util.ImmutableCollections$AbstractImmutableSet
AbstractMap java.util.AbstractMap
Collection java.util.Collection
Iterable java.lang.Iterable
List java.util.List
List12 java.util.ImmutableCollections$List12
ListN java.util.ImmutableCollections$ListN
Map java.util.Map
Map1 java.util.ImmutableCollections$Map1
MapN java.util.ImmutableCollections$MapN
RandomAccess java.util.RandomAccess
SequencedCollection java.util.SequencedCollection
Set java.util.Set
Set12 java.util.ImmutableCollections$Set12
SetN java.util.ImmutableCollections$SetN

其他

文中的类图是如何绘制的?

我在 Java 如何自动生成简单的 Mermaid 类图 一文中分享过可以生成简单 Mermaid 类图的代码,在那些代码的基础上,可以通过执行如下的命令来生成类图 ⬇️

bash 复制代码
java ClassDiagramGenerator -i 'java.io.Serializable' 'java.util.ImmutableCollections$List12' 'java.util.ImmutableCollections$ListN' 'java.util.ImmutableCollections$Set12' 'java.util.ImmutableCollections$SetN' 'java.util.ImmutableCollections$Map1' 'java.util.ImmutableCollections$MapN'

参考资料

相关推荐
Lhappy嘻嘻3 小时前
Java IO|File 文件操作 + 字节流 / 字符流完整笔记 + 递归删除文件实战
java·笔记·php
To_OC3 小时前
手写 AI 编程 Agent 的命令执行工具:我被 child_process 坑出来的实战经验
后端·node.js·agent
伊玛目的门徒3 小时前
试用leetcode之典中典 二数之和问题
java·算法·leetcode
逝水无殇5 小时前
C# 异常处理详解
开发语言·后端·c#
懒鸟一枚6 小时前
深入理解 Linux 内存、Swap 交换分区与分页机制的关系
java·linux·数据库
我命由我123457 小时前
执行 Gradle 指令报错,无法将“grep”项识别为 cmdlet、函数、脚本文件或可运行程序的名称
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
考虑考虑8 小时前
Sentinel安装
java·后端·微服务
IT_陈寒8 小时前
SpringBoot自动配置失灵?你可能忘了这个关键注解
前端·人工智能·后端
碎碎念_4928 小时前
SpringBoot + Vue 前后端分离从 0 到 1 完整环境配置流程
vue.js·spring boot·后端
逝水无殇8 小时前
C# 文件的输入与输出详解
开发语言·数据库·后端·c#