Java List of()静态方法 [英] Java List of() static method

查看:165
本文介绍了Java List of()静态方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在代码片段下面执行

System.out.println(List.of(1, 2).getClass());  
System.out.println(List.of(1, 2, 3).getClass());

此代码的输出为;

class java.util.ImmutableCollections$List2  
class java.util.ImmutableCollections$ListN

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

I am expecting java.util.ImmutableCollections$List3 as output for the second statement because there is of() method which takes three parameter, Why java creating ImmutableCollections$ListN but not ImmutableCollections$List3?

编辑:这是Java-9问题。在List接口中总共有11个重载的()方法,每个方法从0到10采用可变数量的参数,第11个采用varargs来处理N列表。所以我期待前10个重载方法的List0到List10实现,但它返回带有三个参数的ListN。是的,它是实现细节,但只是想了解更多相关信息。

Edited: It is Java-9 question. There are total 11 overloaded of() methods in List interface each of them takes variable number of parameters from zero to 10 and eleventh one takes varargs to handle N list. So I am expecting List0 to List10 implementation for first 10 overloaded methods, but it is returning ListN with three parameters. Yes, it is implementation detail but just curious to know more information of this.

推荐答案

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

The main reason to have several different private implementations of List is to save space.

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

Consider an implementation that stores its elements in an array. (This is essentially what ListN does.) In Hotspot (64-bit with compressed object pointers, each 4 bytes) each object requires a 12-byte header. The ListN object has a single field containing the array, for a total of 16 bytes. An array is a separate object, so that has another 12-byte header plus a 4-byte length. That's another 16 bytes, not counting any actual elements stored. If we're storing two elements, they take 8 bytes. That brings the total to 40 bytes for storing a two-element list. That's quite a bit of overhead!

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

If we were to store the elements of a small list in fields instead of an array, that object would have a header (12 bytes) plus two fields (8 bytes) for a total of 20 bytes -- half the size. For small lists, there's a considerable savings with storing elements in fields of the List object itself instead of in an array, which is a separate object. This is what the old List2 implementation did. It's recently been superseded by the List12 implementation, which can store lists of one or two elements in fields.

现在,在API中有12个重载的 List.of()方法:零到十个固定的args加上varargs。不应该有相应的 List0 通过 List10 ListN 实现?

Now, in the API there are 12 overloaded List.of() methods: zero to ten fixed args plus varargs. Shouldn't there be corresponding List0 through List10 and ListN implementations?

可能有,但不一定必须如此。这些实现的早期原型具有与API相关联的优化小列表实现。因此,()方法的零,一和两个固定arg 创建了 List0 的实例, List1 List2 ,并且varargs List.of()方法创建了一个 ListN 的实例。这是相当简单的,但它是相当严格的。我们希望能够随意添加,删除或重新安排实现。改变API要困难得多,因为我们必须保持兼容。因此,我们决定将事物分离,以便API中的参数数量在很大程度上独立于下面实例化的实现。

There could be, but there doesn't necessarily have to be. An early prototype of these implementations had the optimized small list implementations tied to the APIs. So the zero, one, and two fixed arg of() methods created instances of List0, List1, and List2, and the varargs List.of() method created an instance of ListN. This was fairly straightforward, but it was quite restrictive. We wanted to be able to add, remove, or rearrange implementations at will. It's considerably more difficult to change APIs, since we have to remain compatible. Thus, we decided to decouple things so that the number of arguments in the APIs was largely independent of the implementation instantiated underneath.

在JDK 9中,我们最终得到了API中的12个重载,但只有4个实现:基于字段的实现包含0,1和2个元素,以及一个数组基于任意数字的实现。为什么不添加更多基于字段的实现?收益递减,代码臃肿。大多数列表都有很少的元素,随着元素数量的增加,列表出现时会出现指数下降。与基于阵列的实现相比,节省的空间相对较小。然后是维护所有这些额外实现的问题。它们必须直接输入源代码(庞大)或者我们切换到代码生成方案(复杂)。似乎没有理由。

In JDK 9 we ended up with the 12 overloads in the API, but only four implementations: field-based implementations holding 0, 1, and 2 elements, and an array-based implementation holding an arbitrary number. Why not add more field-based implementations? Diminishing returns and code bloat. Most lists have few elements, and there's an exponential dropoff in the occurrences of lists as the number of elements gets larger. The space savings get relatively smaller compared to an array-based implementation. Then there's the matter of maintaining all those extra implementations. Either they'd have to be entered directly in the source code (bulky) or we'd switch over to a code generation scheme (complex). Neither seemed justified.

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

Our startup performance guru Claes Redestad did some measurements and found that there was a speedup in having fewer list implementations. The reason is megamorphic dispatch. Briefly, if the JVM is compiling the code for a virtual call site and it can determine that only one or two different implementations are called, it can optimize this well. But if there are many different implementations that can be called, it has to go through a slower path. (See this article for Black Magic details.)

对于列表实现,事实证明我们可以通过更少的实现来实现而不会丢失太多空间。 List1 List2 实现可以合并为一个双字段 List12 实现,如果只有一个元素,则第二个字段为null。我们只需要一个零长度列表,因为它是不可变的!对于零长度列表,我们可以摆脱 List0 只使用带有零长度数组的 ListN 。它比旧的 List0 实例更大,但我们不关心,因为它们只有一个。

For the list implementations, it turns out that we can get by with fewer implementations without losing much space. The List1 and List2 implementations can be combined into a two-field List12 implementation, with the second field being null if there's only one element. We only need one zero-length list, since it's immutable! For a zero-length list, we can get rid of List0 just use a ListN with a zero-length array. It's bigger than an old List0 instance, but we don't care, since there's only one of them.

这些更改刚刚进入JDK 11主线。由于API与实现完全分离,因此没有兼容性问题。

These changes just went into the JDK 11 mainline. Since the API is completely decoupled from the implementations, there is no compatibility issue.

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

There are additional possibilities for future enhancements. One potential optimization is to fuse an array onto the end of an object, so the object has a fixed part and a variable-length part. This will avoid the need for an array object's header, and it will probably improve locality of reference. Another potential optimization is with value types. With value types, it might be possible to avoid heap allocation entirely, at least for small lists. Of course, this is all highly speculative. But if new features come along in the JVM, we can take advantage of them in the implementations, since they're are entirely hidden behind the API.

这篇关于Java List of()静态方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆