Java 9中集合的重载便利工厂方法有啥意义

Posted

技术标签:

【中文标题】Java 9中集合的重载便利工厂方法有啥意义【英文标题】:What is the point of overloaded Convenience Factory Methods for Collections in Java 9Java 9中集合的重载便利工厂方法有什么意义 【发布时间】:2017-06-14 14:36:11 【问题描述】:

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)

SetMap 也是如此。

既然有 var args 方法,那多出 11 个方法又有什么意义呢?

我认为 var-args 创建了一个数组,因此其他 11 种方法可以跳过创建额外对象,并且在大多数情况下 0 - 10 个元素就可以了。还有其他原因吗?

【问题讨论】:

你刚刚回答了你自己的问题 - 用 0-10 个参数重载它会跳过不必要的数组创建。 @Marco13 我投票决定重新开放,因为我认为存在很多超越技术的问题。也投票重新开放,因为鉴于这些功能现在在 java 中可用,人们会搜索“java 9 collection.of”而不是“Guava colleciton.of”。 @Marco13 我相信创建一个规范问题并将这两个问题作为重复问题指向它是有意义的。在我们这样做之前,我觉得这个问题应该保持开放。我不想占用这个问题的评论空间来讨论这个问题,所以让我们看看其他人怎么说。 我正在投票重新开放。其他 API(Guava、EnumSet)中类似重载的 justification 不同 - 它们提供重载是因为 @SafeVarargs 当时不存在,而 JEP 269 中重载的驱动程序是 performance。 @StefanZobel 同意;特别是因为一般讨论点是这是一个排除数组创建的微优化,同时所有方法(除了 2 个参数)都委托给这个:@SafeVarargs @SuppressWarnings("unchecked") SetN(E... input) 【参考方案1】:

此模式用于优化接受可变参数的方法。

如果您知道您最多只使用其中几个,您可能希望使用最常用的参数数量定义方法重载:

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);

这将帮助您避免在调用 varargs 方法时创建数组。用于性能优化的模式:

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)... 等的潜在重载。

【讨论】:

【参考方案2】:

根据Java doc:便利工厂方法返回的集合比它们的可变等价物更节省空间。

Java 9 之前:

Set<String> set = new HashSet<>(3);   // 3 buckets

set.add("Hello");
set.add("World");
set = Collections.unmodifiableSet(set);

Set的上述实现中,有6个对象正在创建:不可修改的包装器; HashSet,其中包含一个HashMap;桶表(一个数组);和两个 Node 实例(每个元素一个)。如果 VM 每个对象占用 12 字节,则有 72 字节作为开销消耗,加上 28*2 = 56 字节用于 2 个元素。与存储在集合中的数据相比,这里的大量开销被开销所消耗。但在 Java 9 中,这种开销非常小。

Java 9 之后:

Set<String> set = Set.of("Hello", "World");

Set 的上述实现中,只有一个对象正在创建,由于开销最小,这将占用非常少的空间来保存数据。

【讨论】:

虽然这个答案可能是正确的,但它与所提出的问题无关。【参考方案3】:

正如您所怀疑的,这是一种性能增强。 Vararg 方法“在后台”创建一个数组,并且使用带有 1-10 个参数的方法直接避免了这种冗余数组的创建。

【讨论】:

这难道不是在一个硬件充裕的时代,您可以使用 Java 等高级语言进行微优化吗?我真希望有另一个原因,但想不出一个。 @CKing 语言/库设计者需要考虑他们代码的性能,而不仅仅是不经常运行的代码;还有当有人在计算密集型应用程序的热循环中使用它时会发生什么。在这些情况下,本来可以被视为“毫无意义的微优化”的事情确实会对性能产生重大影响。 @DanNeely 这个论点在这种情况下有效吗?创建一个包含 10 个元素的数组有多贵? @CKing 我的兴趣水平没有上升到编写基准的地步,但@JuBobs answer 中引用的书暗示在某些情况下它可能变得重要。我的假设是,这种影响是间接的——来自额外的垃圾收集——而不是因为直接花费了通过临时数组移动数据的时间。 @DanNeely & @ares :具有讽刺意味的是,hot loops 场景绝对是 considered irrelevant 进行此优化。【参考方案4】:

你也可以反过来看。由于 varargs 方法可以接受数组,因此这种方法可以作为将数组转换为 List 的替代方法。

String []strArr = new String[]"1","2";
List<String> list = List.of(strArr);

这种方法的替代方法是使用Arrays.asList,但在这种情况下对List 所做的任何更改都会反映在数组中,而List.of 则不是这种情况。因此,当您不希望 List 和数组同步时,您可以使用 List.of

注意 规范中给出的理由对我来说似乎是一种微优化。 (这点现在已经由 API 的所有者本人在 cmets 中确认到another 回答)

【讨论】:

这就像不是编译器为我创建一个数组,而是我自己创建一个。 @ares 这样想。您有时会与返回数组的代码交谈。当你想把这个数组转换成一个列表时,你可以直接把它传给List.of方法。此外,与 Arrays.asList 方法相比,List 和数组不会同步,因此这是该方法的另一个用例。【参考方案5】:

来自JEP docs 本身 -

说明 -

这些将包括可变参数重载,因此没有固定限制 关于集合大小。但是,这样创建的集合实例 可能会针对较小的尺寸进行调整。特殊情况 API(固定参数 将提供最多十个元素的重载)。 虽然这 在 API 中引入了一些混乱,它避免了数组分配, 初始化和垃圾收集开销 可变参数调用。 值得注意的是,无论调用固定参数还是可变参数重载,调用站点的源代码都是相同的。


编辑 - 增加动力,正如 @CKing 在 cmets 中已经提到的那样:

非目标 -

支持高性能、可扩展的集合不是目标 具有任意数量的元素。 重点是小型收藏

动机 -

创建一个小的、不可修改的集合(例如,一个集合)包括构造它,将它存储在一个局部变量中,并对其调用 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");

【讨论】:

这难道不是在一个硬件充裕的时代,您可以使用 Java 等高级语言进行微优化吗?我真希望有另一个原因,但想不出一个 @CKing 是的,这是一个微优化。 JDK 库的性能标准比大多数应用程序严格得多。这些 API 在 JDK 本身中使用,它们会影响启动时间,因此即使是看似微小的优化也是值得的。 @StuartMarks 但是他们都(除了两个参数方法)委托给这个:@SafeVarargs @SuppressWarnings("unchecked") SetN(E... input) @Eugene 是的,它隐藏在实现中,并且可以兼容地更改。它只是还没有完全优化。 @CKing API 用于一般用途。但我们希望能够在不牺牲性能的情况下在 JDK 中使用它们。关于新 API 的一般用法,源代码通常不需要知道或关心它是调用固定参数还是可变参数方法。如果您有 Set.of(a1, a2, ... a10) 并且添加了另一个 arg,它最终会从 fixed 切换到 varargs 调用,但除此之外它的行为完全相同。因此,请继续使用新的 API。【参考方案6】:

您可能会发现 Josh Bloch 的 Effective Java(第 2 版)第 42 项的以下段落很有启发性:

每次调用可变参数方法都会导致数组分配和初始化。如果您凭经验确定自己负担不起这笔费用,但您需要可变参数的灵活性,那么有一种模式可以让您既吃又吃。假设您已经确定 95% 的方法调用具有三个或更少的参数。然后声明该方法的五种重载,一种是零到三个普通参数,一种是在参数数量超过三个时使用的单个 varargs 方法[...]

【讨论】:

好的,所以实现是直接从 Josh Bolch 的文本中复制而来的。 需要注意的最重要的两个词:根据经验确定。微优化绝对需要经验证明。

以上是关于Java 9中集合的重载便利工厂方法有啥意义的主要内容,如果未能解决你的问题,请参考以下文章

Java中集合的一些初始化写法

设计模式-----抽象工厂模式

JAVA设计模式--工厂模式

如何用JS获取后台MAP中集合的值

寻找便利工厂来创建 GroovyObjectSupport 实例

Java设计模式——工厂方法模式