在Java 9中重载的方便工厂方法的重点是什么?

Java 9附带了用于创建不可变列表的便捷工厂方法 。 最后,列表创建非常简单:

List list = List.of("foo", "bar"); 

但是这个方法有12个重载版本,11个有0到10个元素,还有一个有var args。

 static  List of(E... elements) 

SetMap的情况也是如此。

由于存在var args方法,有多少11个方法有什么意义呢?

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

来自JEP文档本身 –

描述

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


编辑 – 为了增加动力,正如@CKing在评论中已提到的那样:

非目标

支持具有任意数量元素的高性能,可伸缩集合并非目标。 重点是小集合

动机

创建一个小的,不可修改的集合(比如一组)包括构造它,将它存储在局部变量中,并在其上多次调用add(),然后将其包装起来。

 Set set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a", "b", "c"))); 

通过组合流工厂方法和收集器,Java 8 Stream API可用于构建小型集合。

 // Java 8 Set set1 = Collections.unmodifiableSet(Stream.of("a", "b", "c").collect(Collectors.toSet())); 

通过提供用于创建小型集合实例的库API,与更改语言相比,成本和风险显着降低,可以获得集合文字的大部分好处。 例如,创建小型Set实例的代码可能如下所示:

 // Java 9 Set set2 = Set.of("a", "b", "c"); 

如您所料,这是性能增强。 Vararg方法在“引擎盖下”创建一个数组,并且具有直接采用1-10个参数的方法避免了这种冗余的数组创建。

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

每次调用varargs方法都会导致数组分配和初始化。 如果你凭经验确定你无法承担这笔费用,但是你需要varargs的灵活性,那么有一种模式可以让你吃蛋糕并吃掉它。 假设您已确定95%的方法调用具有三个或更少的参数。 然后声明方法的五个重载,一个用零到三个普通参数,以及一个varargs方法,当参数个数超过三个时使用[…]

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

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

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

注意规范中给出的理由对我来说似乎是一种微观优化。 (现在已经由API的所有者在另一个答案的评论中证实了这一点)

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

如果你能够找出你只使用其中几个的大部分时间,你可能想要用最常用参数的数量来定义一个方法过载:

 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 list = List.of("foo", "bar"); // Delegates call here static  List of(E e1, E e2) { return new ImmutableCollections.List2<>(e1, e2); // Constructor with 2 parameters, varargs avoided! } 

这背后更有趣的是从3个参数开始我们再次委托给varargs构造函数:

 static  List of(E e1, E e2, E e3) { return new ImmutableCollections.ListN<>(e1, e2, e3); // varargs constructor } 

这似乎现在很奇怪,但正如我猜测 – 这是为未来的改进而保留的,作为一个选项,所有构造函数List3(3 params), List7(7 params)...等的潜在重载。

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

在Java 9之前:

 Set 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 set = Set.of("Hello", "World"); 

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