首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Java List of()静态方法

Java List of()静态方法
EN

Stack Overflow用户
提问于 2018-03-21 18:18:52
回答 4查看 2.5K关注 0票数 2

我正在执行下面的代码片段

代码语言:javascript
复制
System.out.println(List.of(1, 2).getClass());  
System.out.println(List.of(1, 2, 3).getClass());

此代码的输出为;

代码语言:javascript
复制
class java.util.ImmutableCollections$List2  
class java.util.ImmutableCollections$ListN

我希望java.util.ImmutableCollections$List3作为第二个语句的输出,因为有一个带有三个参数的of()方法,为什么java创建ImmutableCollections$ListN而不是ImmutableCollections$List3

编辑:这是Java-9问题。list接口中总共有11个重载的()方法,每个方法都接受从0到10的可变数量的参数,第11个方法使用varargs来处理N个列表。因此,我期望前10个重载方法的List0 to List10实现,但它返回的是带有三个参数的ListN。是的,这是实现的细节,但只是好奇地想知道更多的信息。

EN

回答 4

Stack Overflow用户

发布于 2018-03-28 15:02:46

拥有几种不同的List私有实现的主要原因是为了节省空间。

考虑一个将其元素存储在数组中的实现。(这基本上就是ListN所做的。)在Hotspot (64位压缩对象指针,每个4字节)中,每个对象都需要一个12字节的标头。ListN对象有一个包含数组的字段,总共16个字节。数组是一个单独的对象,所以它有另外一个12字节的头加上4字节的长度。这又是16个字节,不包括任何实际存储的元素。如果我们存储两个元素,它们需要8个字节。这使得用于存储两个元素列表的总字节数达到40字节。这是一个相当大的开销!

如果我们将一个小列表的元素存储在字段中,而不是数组中,那么该对象将有一个头(12个字节)加上两个字段(8个字节),总共20个字节--大小的一半。对于较小的列表,将元素存储在List对象本身的字段中,而不是存储在一个单独的对象数组中,可以节省相当多的成本。这就是旧的List2实现所做的事情。它最近被List12实现所取代,后者可以在字段中存储一个或两个元素的列表。

现在,API中有12个重载的List.of()方法:0到10个固定参数加上可变参数。在List10ListN实现中不应该有相应的List0吗?

可能会有,但不一定非得有。这些实现的早期原型将优化的小列表实现绑定到API。因此,0、1和2个固定的arg of()方法创建了List0List1List2的实例,而varargs List.of()方法创建了ListN的实例。这是相当简单的,但也有相当多的限制。我们希望能够随意添加、删除或重新排列实现。更改API要困难得多,因为我们必须保持兼容。因此,我们决定将事物解耦,以便API中的参数数量在很大程度上独立于底层实例化的实现。

在JDK 9中,我们最终实现了API中的12个重载,但只有4个实现:基于字段的实现包含0、1和2元素,基于数组的实现包含任意数字。为什么不添加更多基于字段的实现呢?回报递减和代码膨胀。大多数列表只有很少的元素,并且随着元素数量的增加,列表的出现次数呈指数下降。与基于数组的实现相比,节省的空间相对较小。然后就是维护所有这些额外实现的问题。它们要么必须直接在源代码中输入(笨重),要么我们会切换到代码生成方案(复杂)。这两种说法似乎都没有道理。

我们的创业表现大师Claes Redestad做了一些测量,发现列表实现越少,速度就越快。原因是巨型分派。简而言之,如果JVM正在编译虚拟调用点的代码,并且它可以确定只调用了一两个不同的实现,那么它可以很好地优化这一点。但是,如果有许多不同的实现可以调用,它必须通过较慢的路径。(有关Black Magic的详细信息,请参阅本文。)

对于list实现,事实证明,我们可以使用更少的实现,而不会损失太多空间。可以将List1List2实现组合成两个字段的List12实现,如果只有一个元素,则第二个字段为null。我们只需要一个零长度的列表,因为它是不可变的!对于零长度列表,我们可以去掉List0,只需使用具有零长度数组的ListN即可。它比旧的List0实例更大,但我们并不关心,因为它们只有一个。

这些更改刚刚进入JDK 11主线。由于API与实现完全解耦,因此不存在兼容性问题。

未来的增强功能还有更多的可能性。一种可能的优化是将数组融合到对象的末尾,这样对象就有一个固定的部分和一个可变长度的部分。这将避免需要数组对象的头,并且它可能会改善引用的局部性。另一个潜在的优化是值类型。对于值类型,可能完全避免堆分配,至少对于较小的列表是这样。当然,这一切都是高度投机的。但是如果JVM中出现了新特性,我们可以在实现中利用它们,因为它们完全隐藏在API后面。

票数 9
EN

Stack Overflow用户

发布于 2018-03-21 18:31:30

ImmutableCollections$List2ImmutableCollections$ListN都不是在运行时生成的。已经编写了四个类:

代码语言:javascript
复制
static final class List0<E> extends AbstractImmutableList<E> { ... }
static final class List1<E> extends AbstractImmutableList<E> { ... }
static final class List2<E> extends AbstractImmutableList<E> { ... }
static final class ListN<E> extends AbstractImmutableList<E> { ... }

of(E e1, E e2, E e3)of(E e1, ..., E e10),我们将创建一个ImmutableCollections.ListN<>实例。

为什么java创建ImmutableCollections$ListN而不是ImmutableCollections$List3

设计者可能已经决定3N的情况很相似,不值得为3编写单独的类。显然,他们不会像从$List0$List1$List2版本中获得的那样,从$List3$List7$List10中获得足够的好处。它们是专门优化的。

目前,4个类涵盖了10个方法。如果他们决定添加更多的方法(例如with 22 arguments),仍然会有这4个类。假设您正在为22个方法编写22个类。它会涉及多少不必要的代码重复?

票数 3
EN

Stack Overflow用户

发布于 2018-03-21 18:29:15

这两个类都是被返回的。也就是说,ImmutableCollections$List2ImmutableCollections$ListN有一个单独的类($表示内部类)

这是一个实现细节,(假设) List2的存在(可能)是出于某种优化原因。我怀疑,如果你查看源代码(通过IDE或类似的),你会看到两个截然不同的内部类。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/49403644

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档