Arrays.asList() 不能正常工作?

Posted

技术标签:

【中文标题】Arrays.asList() 不能正常工作?【英文标题】:Arrays.asList() not working as it should? 【发布时间】:2010-11-30 20:59:24 【问题描述】:

我有一个 float[],我想获得一个包含相同元素的列表。我可以做一件丑陋的事情,将它们一一添加,但我想使用 Arrays.asList 方法。不过有一个问题。这有效:

List<Integer> list = Arrays.asList(1,2,3,4,5);

但事实并非如此。

int[] ints = new int[] 1,2,3,4,5;
List<Integer> list = Arrays.asList(ints);

asList 方法接受一个 varargs 参数,据我所知,该参数是数组的“速记”。

问题:

为什么第二段代码返回List&lt;int[]&gt;而不是List&lt;int&gt;

有办法改正吗?

为什么自动装箱在这里不起作用;即int[]Integer[]

【问题讨论】:

顺便说一句:float[] 数组发生了什么? 【参考方案1】:

在 Java 中没有 List&lt;int&gt; 这样的东西 - 泛型不支持原语。

自动装箱只发生在单个元素上,而不是 数组 的基元。

至于如何纠正它 - 有各种各样的库有大量的方法来做这样的事情。没有办法解决这个问题,而且我认为在 JDK 中没有任何东西可以让它变得更容易。有些会在包装类型列表中包装一个原始数组(以便在访问时发生装箱),其他人将遍历原始数组以创建一个独立的副本,并在进行时装箱。确保你知道你在使用哪个。

(编辑:我一直假设int[] 的起点是不可协商的。如果你可以从Integer[] 开始,那么你就很远了:)

仅举一个帮助程序库的示例,并插入Guava 一点,有com.google.common.primitive.Ints.asList

【讨论】:

为什么“自动装箱只发生在单个元素,而不是基元数组。”。您认为做出这一设计决定的原因是什么? @Geek:那么您会建议什么替代方案?通过复制所有元素将double[] 自动转换为Double[] 是很奇怪的——这样引用类型的转换将有效地克隆数据,这与Java 中的所有其他操作不同。如果您建议Double[] 应该能够以“视图”的方式得到double[] 的支持,那还有其他问题,例如如何存储空引用以及此数组与其他数组之间的区别.我认为这是正确的决定。 使用 java 8 你可以使用流 api: List list = Arrays.stream(new int[]1,2,3).boxed().collect(Collectors.toList( ));,虽然有点冗长 其中一个 Guava 函数早就应该添加到 Java 中了。不幸的是,可怕的类型与原始类型的杂耍不会很快消失。 警告:Ints.asList 返回的列表不支持add() 或相关方法【参考方案2】:

这个怎么样?

Integer[] ints = new Integer[] 1,2,3,4,5;
List<Integer> list = Arrays.asList(ints);

【讨论】:

打败我,但最好也有解释。 难以置信...它适用于参考,但不适用于原始类型:D 非常感谢。 :) 嗯,但是有没有一种简单的方法可以将 int[] 转换为 Integer[]?问题是我通过方法调用获得了我的数组,但我无法更改它。 @Savvas:请参阅***.com/questions/880581/java-convert-int-to-integer 或 Jon Skeet 的答案。 Apache Commons Lang 或 Guava 等库会有所帮助。 可以克服警告 类型安全:类型 List 的表达式需要未经检查的转换以符合 List ?【参考方案3】:

因为 java 数组是对象,Arrays.asList() 将您的 int 数组视为可变参数列表中的 单个 参数。

【讨论】:

@mmyers 事实上,这就是这个问题的全部内容。 +1 为 ChssPly76【参考方案4】:

进入Java 8,你可以做以下收集到一个盒装数组中:

Integer[] boxedInts = IntStream.of(ints).boxed().toArray(Integer[]::new);

或者这个收集在一个装箱的列表中

List<Integer> boxedInts = IntStream.of(ints).boxed().collect(Collectors.toList());

但是,这仅适用于 int[]long[]double[]。这不适用于byte[]

注意Arrays.stream(ints)IntStream.of(ints) 是等价的。所以前面两个例子也可以改写为:

Integer[] boxedIntArray = Arrays.stream(ints).boxed().toArray(Integer[]::new);
List<Integer> boxedIntList = Arrays.stream(ints).boxed().collect(Collectors.toList());

最后一种形式可能会受到青睐,因为它省略了Stream 的原始特定子类型。然而,在内部它仍然是一堆重载的,在这种情况下仍然在内部创建一个IntStream

【讨论】:

您需要从toArray() 转换结果,因为它返回Object[] 不.. docs.oracle.com/javase/8/docs/api/java/util/stream/… 好的。你说的对。此时输入的是Stream&lt;Integer&gt;,而不是IntStream。但是,我已经通过使用 toArray(Integer[]::new) 来修复它。也许在这里简单地进行类型转换会更好? 这实际上是一个不同的 重载toArray() 实现(docs.oracle.com/javase/8/docs/api/java/util/stream/…),它使用了一个生成器函数 并正确地完成了工作。最好尽量避免类型转换..【参考方案5】:

问题不在于Arrays.asList()。问题是您希望自动装箱可以在数组上工作 - 但事实并非如此。在第一种情况下,编译器在查看它们的用途之前对各个 int 进行自动装箱。在第二种情况下,您首先将它们放入一个 int 数组(不需要自动装箱),然后将其传递给 Arrays.asList()(不能自动装箱)。

【讨论】:

【参考方案6】:

为什么自动装箱在这里不起作用;即 int[] 到 Integer[]?

虽然自动装箱会将int 转换为Integer,但它不会将int[] 转换为Integer[]

为什么不呢?

简单(但不令人满意)的答案是因为 JLS 就是这么说的。 (如果你喜欢,你可以检查它。)

真正的答案是自动装箱的作用及其安全性的基础。

当您在代码中的任意位置自动装箱 1 时,您会得到相同的 Integer 对象。并非所有int 值都是如此(由于Integer 自动装箱缓存的大小有限),但如果您使用equals 比较Integer 对象,您会得到“正确”的答案。

基本上N == N 始终为真,new Integer(N).equals(new Integer(N)) 始终为真。此外,这两件事保持真实......假设您坚持使用纯 Java 代码。

现在考虑一下:

int[] x = new int[]1;
int[] y = new int[]1;

这些相等吗?不! x == y 是假的,x.equals(y) 是假的!但为什么?因为:

y[0] = 2;

换句话说,两个具有相同类型、大小和内容的数组总是可以区分的,因为 Java 数组是可变的。

自动装箱的“承诺”是可以这样做,因为结果无法区分1。但是,由于数组和数组可变性的equals 的定义,所有数组基本上都是可区分的。因此,如果允许对原始类型数组进行自动装箱,则会破坏“承诺”。


1 - ..... 前提是您不使用 == 来测试自动装箱值是否相等。

【讨论】:

【参考方案7】:

Arrays.asList(T... a) 有效地将T[] 作为一个数组匹配任何真实对象数组(Object 的子类)。唯一不匹配的是基元数组,因为基元类型不是从Object 派生的。所以int[] 不是Object[]

然后发生的事情是 varags 机制启动并将其视为您传递了单个对象,并创建了该类型的单个元素数组。因此,您传递了一个 int[][](这里,Tint[])并最终得到一个 1 元素 List&lt;int[]&gt;,这不是您想要的。

不过,您仍然有一些不错的选择:

Guava 的 Int.asList(int[]) 适配器

如果您的项目已经使用了 guava,那么只需使用 Guava 提供的适配器即可:Int.asList()。关联类中的每个原始类型都有一个类似的适配器,例如,Booleans 用于 boolean 等。

int foo[] = 1,2,3,4,5;
Iterable<Integer> fooBar = Ints.asList(foo);
for(Integer i : fooBar) 
    System.out.println(i);

这种方法的优点是它在现有数组周围创建了一个瘦包装器,因此包装器的创建是恒定时间(不依赖于数组的大小),并且所需的存储空间很小除了底层整数数组之外,还有一个常量(小于 100 字节)。

缺点是访问每个元素需要对底层int进行装箱操作,设置需要拆箱。如果您频繁访问列表,这可能会导致大量的临时内存分配。如果您平均多次访问每个对象,则最好使用将对象装箱一次并将它们存储为Integer 的实现。下面的解决方案就是这样做的。

Java 8 内部流

在 Java 8 中,您可以使用 Arrays.stream(int[]) 方法将 int 数组转换为 Stream。根据您的用例,您可以直接使用流,例如,使用forEach(IntConsumer) 对每个元素执行某些操作。在这种情况下,此解决方案非常快,根本不会产生任何装箱或拆箱,并且不会创建底层数组的任何副本。

或者,如果您真的需要List&lt;Integer&gt;,您可以使用stream.boxed().collect(Collectors.toList()) 作为suggested here。这种方法的缺点是它将列表中的每个元素完全装箱,这可能会增加其内存占用近一个数量级,它会创建一个新的Object[] 来保存所有装箱的元素。如果您随后大量使用该列表并需要 Integer 对象而不是 ints,这可能会有所回报,但需要注意。

【讨论】:

【参考方案8】:

如果您将int[] 传递给Arrays.asList(),则创建的列表将是List&lt;int[]&gt;,这在java 中无效,而不是正确的List&lt;Integer&gt;

我认为您希望 Arrays.asList() 自动装箱您的整数,正如您所见,它不会。

【讨论】:

【参考方案9】:

无法将int[] 转换为Integer[],您必须复制值


int[] tab = new int[]1, 2, 3, 4, 5;
List<Integer> list = ArraysHelper.asList(tab);

public static List<Integer> asList(int[] a) 
    List<Integer> list = new ArrayList<Integer>();
    for (int i = 0; i < a.length && list.add(a[i]); i++);
    return list;

【讨论】:

【参考方案10】:

或者,您可以使用 IntList 作为类型,并使用来自 Eclipse Collections 的 IntLists factory 直接从 int 值的数组创建集合。这消除了对intInteger 的任何装箱的需要。

IntList intList1 = IntLists.mutable.with(1,2,3,4,5);
int[] ints = new int[] 1,2,3,4,5;
IntList intList2 = IntLists.mutable.with(ints);
Assert.assertEquals(intList1, intList2);

Eclipse Collections 支持可变和不可变原语List 以及SetBagStackMap

注意:我是 Eclipse Collections 的提交者。

【讨论】:

【参考方案11】:

int 是原始类型。 Arrays.asList() 接受泛型类型 T,它仅适用于引用类型(对象类型),不适用于原语。由于 int[] 作为一个整体是一个对象,它可以作为单个元素添加。

【讨论】:

以上是关于Arrays.asList() 不能正常工作?的主要内容,如果未能解决你的问题,请参考以下文章

Arrays.asList()用法梳理

为什么Java里的Arrays.asList不能用add和remove方法?

Arrays.asList 为什么不能 add 或者 remove 而 ArrayList 可以

Arrays.asList的用法

为啥Java里的Arrays.asList不能用add和remove方法

Java Arrays.asList