Java 9 中集合的重载便利工厂方法有什么意义
What is the point of overloaded Convenience Factory Methods for Collections in Java 9
Java 9 带有 convenience factory methods 用于创建不可变列表。最后,创建列表非常简单:
List<String> list = List.of("foo", "bar");
但是这个方法有 12 个重载版本,11 个有 0 到 10 个元素,一个有 var args。
static <E> List<E> of(E... elements)
Set
和Map
也是如此。
既然有一个 var args 方法,那么多出 11 个方法有什么意义呢?
我认为 var-args 创建一个数组,因此其他 11 种方法可以跳过额外对象的创建,在大多数情况下 0 - 10 个元素就可以了。还有其他原因吗?
如您所料,这是一项性能增强。 Vararg 方法创建一个数组 "under the hood",并且具有直接采用 1-10 个参数的方法避免了这种冗余数组创建。
来自 JEP docs 本身 -
描述 -
These will include varargs overloads, so that there is no fixed limit
on the collection size. However, the collection instances so created
may be tuned for smaller sizes. Special-case APIs (fixed-argument
overloads) for up to ten of elements will be provided. While this
introduces some clutter in the API, it avoids array allocation,
initialization, and garbage collection overhead that is incurred by
varargs calls. Significantly, the source code of the call site is the same regardless of whether a fixed-arg or varargs overload is called.
编辑 - 增加动力,正如@CKing 在评论中已经提到的那样:
Non-Goals -
It is not a goal to support high-performance, scalable collections
with arbitrary numbers of elements. The focus is on small collections.
动机 -
创建一个小的、不可修改的集合(比如,一个集合)包括构造它,将它存储在局部变量中,多次调用它的 add(),然后包装它。
Set<String> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a", "b", "c")));
通过结合流工厂方法和收集器,Java 8 Stream API 可用于构建小型集合。
// Java 8
Set<String> set1 = Collections.unmodifiableSet(Stream.of("a", "b", "c").collect(Collectors.toSet()));
通过提供用于创建小型集合实例的库 API,可以获得集合文字的大部分好处,与更改语言相比,成本和风险显着降低。例如,创建小型 Set 实例的代码可能如下所示:
// Java 9
Set set2 = Set.of("a", "b", "c");
你也可以反过来看。由于 varargs 方法可以接受数组,因此这种方法可以作为将数组转换为 List
.
的替代方法
String []strArr = new String[]{"1","2"};
List<String> list = List.of(strArr);
此方法的替代方法是使用 Arrays.asList
,但在这种情况下对 List
所做的任何更改都会反映在数组中,而 List.of
则不是这种情况。因此,当您不希望 List
和数组同步时,您可以使用 List.of
。
注意 规范中给出的理由对我来说似乎是 micro-optimzation。 (现在 API 的所有者本人在对 回答的评论中证实了这一点)
您可能会发现 Josh Bloch 的 Effective Java(第 2 版)第 42 项的以下段落很有启发性:
Every invocation of a varargs method causes an array allocation and initialization. If you have determined empirically that you can’t afford this cost but you need the flexibility of varargs, there is a pattern that lets you have your cake and eat it too. Suppose you’ve determined that 95 percent of the calls to a method have three or fewer parameters. Then declare five overloadings of the method, one each with zero through three ordinary parameters, and a single varargs method for use when the number of arguments exceeds three [...]
根据 Java doc:便利工厂方法返回的集合比它们的 mutable 等价物更 space 高效。
之前 Java 9:
Set<String> set = new HashSet<>(3); // 3 buckets
set.add("Hello");
set.add("World");
set = Collections.unmodifiableSet(set);
在上述 Set
的实现中,创建了 6 个对象:不可修改的包装器; HashSet
,其中包含一个HashMap
; table 个桶(一个数组);和两个 Node 实例(每个元素一个)。如果 VM 每个对象占用 12 字节,则有 72 字节作为开销消耗,加上 28*2 = 56 字节用于 2 个元素。与存储在集合中的数据相比,这里的大量开销被消耗掉了。但是在 Java 9 中,这种开销非常小。
Java9 之后:
Set<String> set = Set.of("Hello", "World");
在上述 Set
的实现中,只有一个对象正在创建,由于开销最小,这将花费 space 非常少的时间来保存数据。
此模式用于优化接受可变参数的方法。
如果您知道大多数时间您只使用其中的几个,您可能想要定义一个方法重载,其中包含最常用的参数数量:
public void foo(int num1);
public void foo(int num1, int num2);
public void foo(int num1, int num2, int num3);
public void foo(int... nums);
这将帮助您避免在调用可变参数方法时创建数组。用于性能优化的模式:
List<String> list = List.of("foo", "bar");
// Delegates call here
static <E> List<E> of(E e1, E e2) {
return new ImmutableCollections.List2<>(e1, e2); // Constructor with 2 parameters, varargs avoided!
}
这背后更有趣的是,从 3 个参数开始,我们再次委托给 varargs 构造函数:
static <E> List<E> of(E e1, E e2, E e3) {
return new ImmutableCollections.ListN<>(e1, e2, e3); // varargs constructor
}
目前这看起来很奇怪,但正如我猜测的那样 - 这是为将来的改进保留的,并且作为一个选项,所有构造函数的潜在重载 List3(3 params), List7(7 params)...
等
Java 9 带有 convenience factory methods 用于创建不可变列表。最后,创建列表非常简单:
List<String> list = List.of("foo", "bar");
但是这个方法有 12 个重载版本,11 个有 0 到 10 个元素,一个有 var args。
static <E> List<E> of(E... elements)
Set
和Map
也是如此。
既然有一个 var args 方法,那么多出 11 个方法有什么意义呢?
我认为 var-args 创建一个数组,因此其他 11 种方法可以跳过额外对象的创建,在大多数情况下 0 - 10 个元素就可以了。还有其他原因吗?
如您所料,这是一项性能增强。 Vararg 方法创建一个数组 "under the hood",并且具有直接采用 1-10 个参数的方法避免了这种冗余数组创建。
来自 JEP docs 本身 -
描述 -
These will include varargs overloads, so that there is no fixed limit on the collection size. However, the collection instances so created may be tuned for smaller sizes. Special-case APIs (fixed-argument overloads) for up to ten of elements will be provided. While this introduces some clutter in the API, it avoids array allocation, initialization, and garbage collection overhead that is incurred by varargs calls. Significantly, the source code of the call site is the same regardless of whether a fixed-arg or varargs overload is called.
编辑 - 增加动力,正如@CKing 在评论中已经提到的那样:
Non-Goals -
It is not a goal to support high-performance, scalable collections with arbitrary numbers of elements. The focus is on small collections.
动机 -
创建一个小的、不可修改的集合(比如,一个集合)包括构造它,将它存储在局部变量中,多次调用它的 add(),然后包装它。
Set<String> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a", "b", "c")));
通过结合流工厂方法和收集器,Java 8 Stream API 可用于构建小型集合。
// Java 8
Set<String> set1 = Collections.unmodifiableSet(Stream.of("a", "b", "c").collect(Collectors.toSet()));
通过提供用于创建小型集合实例的库 API,可以获得集合文字的大部分好处,与更改语言相比,成本和风险显着降低。例如,创建小型 Set 实例的代码可能如下所示:
// Java 9
Set set2 = Set.of("a", "b", "c");
你也可以反过来看。由于 varargs 方法可以接受数组,因此这种方法可以作为将数组转换为 List
.
String []strArr = new String[]{"1","2"};
List<String> list = List.of(strArr);
此方法的替代方法是使用 Arrays.asList
,但在这种情况下对 List
所做的任何更改都会反映在数组中,而 List.of
则不是这种情况。因此,当您不希望 List
和数组同步时,您可以使用 List.of
。
注意 规范中给出的理由对我来说似乎是 micro-optimzation。 (现在 API 的所有者本人在对
您可能会发现 Josh Bloch 的 Effective Java(第 2 版)第 42 项的以下段落很有启发性:
Every invocation of a varargs method causes an array allocation and initialization. If you have determined empirically that you can’t afford this cost but you need the flexibility of varargs, there is a pattern that lets you have your cake and eat it too. Suppose you’ve determined that 95 percent of the calls to a method have three or fewer parameters. Then declare five overloadings of the method, one each with zero through three ordinary parameters, and a single varargs method for use when the number of arguments exceeds three [...]
根据 Java doc:便利工厂方法返回的集合比它们的 mutable 等价物更 space 高效。
之前 Java 9:
Set<String> set = new HashSet<>(3); // 3 buckets
set.add("Hello");
set.add("World");
set = Collections.unmodifiableSet(set);
在上述 Set
的实现中,创建了 6 个对象:不可修改的包装器; HashSet
,其中包含一个HashMap
; table 个桶(一个数组);和两个 Node 实例(每个元素一个)。如果 VM 每个对象占用 12 字节,则有 72 字节作为开销消耗,加上 28*2 = 56 字节用于 2 个元素。与存储在集合中的数据相比,这里的大量开销被消耗掉了。但是在 Java 9 中,这种开销非常小。
Java9 之后:
Set<String> set = Set.of("Hello", "World");
在上述 Set
的实现中,只有一个对象正在创建,由于开销最小,这将花费 space 非常少的时间来保存数据。
此模式用于优化接受可变参数的方法。
如果您知道大多数时间您只使用其中的几个,您可能想要定义一个方法重载,其中包含最常用的参数数量:
public void foo(int num1);
public void foo(int num1, int num2);
public void foo(int num1, int num2, int num3);
public void foo(int... nums);
这将帮助您避免在调用可变参数方法时创建数组。用于性能优化的模式:
List<String> list = List.of("foo", "bar");
// Delegates call here
static <E> List<E> of(E e1, E e2) {
return new ImmutableCollections.List2<>(e1, e2); // Constructor with 2 parameters, varargs avoided!
}
这背后更有趣的是,从 3 个参数开始,我们再次委托给 varargs 构造函数:
static <E> List<E> of(E e1, E e2, E e3) {
return new ImmutableCollections.ListN<>(e1, e2, e3); // varargs constructor
}
目前这看起来很奇怪,但正如我猜测的那样 - 这是为将来的改进保留的,并且作为一个选项,所有构造函数的潜在重载 List3(3 params), List7(7 params)...
等