如何在Java中将List<Integer>转换为int[]?

767

如何在Java中将List<Integer>转换为int[]

我感到困惑的是,List.toArray()实际上返回一个Object[],无法强制转换为Integer[]int[]

现在我正在使用循环来完成这个任务:

int[] toIntArray(List<Integer> list) {
  int[] ret = new int[list.size()];
  for(int i = 0; i < ret.length; i++)
    ret[i] = list.get(i);
  return ret;
}

有没有更好的方法来做这件事?

这类似于问题 如何在Java中将int[]转换为Integer[]?


11
你只能使用以下代码将列表转换为整数数组:Integer[] arr = (Integer[])list.toArray(new Integer[list.size]); - Hardcoded
1
@Hardcoded,你可能想要编辑你的评论,使用list.size()方法并且去掉不必要的转换。 - sactiw
2
现在在Java 8中有更好的方法吗? - Makoto
2
(@Makoto:请查看[Pshemo的答案](https://dev59.com/cXNA5IYBdhLWcg3wcNbF#23945015)) - greybeard
7
int[] arr = listOfIntegers.stream().mapToInt(x->x).toArray(); - old_soul_on_the_run
显示剩余3条评论
16个回答

1048
使用Java 8中新增的流(Stream)功能,我们可以编写如下代码:
int[] example1 = list.stream().mapToInt(i->i).toArray();
// OR
int[] example2 = list.stream().mapToInt(Integer::intValue).toArray();

思考过程:

简单的`Stream#toArray`方法返回一个`Object[]`数组,不是我们想要的。同时,返回`A[]`的`Stream#toArray(IntFunction generator)`方法也无法解决问题,因为泛型类型`A`不能表示原始类型`int`。
所以最好有一种流来处理原始类型`int`,而不是像`Integer`这样的引用类型,因为它的`toArray`方法很可能也会返回一个`int[]`数组(返回其他类型如`Object[]`甚至装箱的`Integer[]`对于`int`来说是不自然的)。幸运的是,Java 8有这样一种流,即IntStream
现在我们唯一需要解决的问题是如何将从`list.stream()`返回的`Stream`转换为那个闪亮的`IntStream`。
在寻找返回`IntStream`的方法时,我们可以快速搜索`Stream`文档,指向我们的解决方案,即mapToInt(ToIntFunction mapper)方法。我们需要做的就是提供从`Integer`到`int`的映射。
由于ToIntFunction是一个函数接口,我们也可以通过lambda表达式或方法引用提供它的实例。
无论如何,我们可以使用Integer#intValue将`Integer`转换为`int`,因此在`mapToInt`中我们可以写:
``` mapToInt((Integer i) -> i.intValue()) ```
(或者某些人可能更喜欢:`mapToInt(Integer::intValue)`。)
但是,类似的代码也可以通过解包生成,因为编译器知道这个lambda的结果必须是`int`类型(在`mapToInt`中使用的lambda是`ToIntFunction`接口的实现,它期望一个返回`int`的方法作为其正文: `int applyAsInt(T value)`,这个方法应该返回一个`int`)。
因此,我们可以简单地编写:
``` mapToInt((Integer i)->i) ```
另外,由于编译器可以推断出`Stream#stream()`返回一个`Stream`,因此可以省略`(Integer i)`中的`Integer`类型,留下
``` mapToInt(i -> i) ```

7
显然是最好的解决方案,可惜缺乏解释。 - Pimp Trizkit
22
@PimpTrizkit稍微更新了这个答案。希望现在更加清晰了。 - Pshemo
3
@Pshemo - 谢谢!我个人其实并不需要解释。但我很讨厌看到完美的答案没有解释!无论如何,您的解释确实教育了我并且很有帮助。我想知道为什么 mapTo... 函数不能允许空的 lambda 参数……就像 sort 函数一样……这样它就会默认使用默认行为……在这种情况下,i -> i 将是一个完美的默认行为。 - Pimp Trizkit
我猜这个答案比ColinD使用Guava的答案更快,对吗? - vefthym
2
@vefthym 我没有测试过,但我怀疑这两个解决方案都基于同样的简单原理,因此我预计速度会相似(但请随意进行基准测试)。这个答案的一个优点是,只要我们有Java 8,它就不需要额外的库。 - Pshemo
显示剩余7条评论

261

很遗憾,由于Java处理基本类型、装箱、数组和泛型的机制,我认为实际上没有更好的方法可行。具体来说:

  • List<T>.toArray不起作用,因为没有从Integerint的转换。
  • 您无法在泛型中使用int作为类型参数,因此必须是一个特定于int的方法(或者使用反射进行诡计操作的方法)。

我相信有一些库已经自动生成了所有基本类型的这种方法的版本(即有一个模板,每种类型都有一个副本)。虽然它很丑陋,但这就是现实 :(

即使Arrays类在Java引入泛型之前就已经出现了,但如果今天引入它(假设您要使用基本数组),它仍将包含所有可怕的重载。


33
请参考ColinD关于Guava的Ints.toArray(Collection<Integer>)方法的答案。 - ron
9
您的意思是,就像Arrays类中已经存在的binarySearchcopyOfcopyOfRange这些可怕的重载一样吗?我想知道为什么他们不能再添加一组可怕的重载。 - Simon Forsberg
1
@ron ColinD的回答除了使用for循环将原始数组填充到非原始数组中之外,没有给出任何新的东西,而这已经是OP拥有的了。 - Tomáš Zato
与此同时,在Oracle,Java工程师们着迷于使用模块使语言过于复杂化... - Gubatron
2
@bvdb:我的意思是,如果没有其他库(这些库基本上仍然有循环,但在它们的代码中而不是你的代码中),我不相信有更好的方法。这比说我不知道是否有更好的方法要强烈得多。 - Jon Skeet
...直到Java语言设计师改进它,否则你无法避免使用基本数据类型 - https://dev59.com/rWYq5IYBdhLWcg3wcgPq - MasterJoe

222

除了Commons Lang之外,您还可以使用Guava的方法Ints.toArray(Collection<Integer> collection)来实现:

List<Integer> list = ...
int[] ints = Ints.toArray(list);

这样可以避免你自己进行Commons Lang等效方法所需的中间数组转换。


4
不幸的是,中间数组被隐藏在Guava内部: Object[] boxedArray = collection.toArray(); - kamczak
9
“幸运的是,中间数组被隐藏在Guava内部。” - 已为您修正 ;) - Eddified

182

最简单的方法是利用Apache Commons Lang。它有一个方便的ArrayUtils类可以实现你想要的功能。使用Integer数组的重载版本,调用toPrimitive方法即可。

List<Integer> myList;
 ... assign and fill the list
int[] intArray = ArrayUtils.toPrimitive(myList.toArray(new Integer[myList.size()]));

这样做可以避免重复造轮子。Commons Lang有很多Java没有的有用工具。上面,我选择创建了一个正确大小的Integer列表。您还可以使用一个长度为0的静态Integer数组,让Java分配一个正确大小的数组:

static final Integer[] NO_INTS = new Integer[0];
   ....
int[] intArray2 = ArrayUtils.toPrimitive(myList.toArray(NO_INTS));

toPrimitive链接已损坏。 - Mark Byers
这是2.6 Commons Lang API的链接:toPrimitive - StackEng2010
12
请注意,这将涉及2次分配和复制:myList.toArray() 将创建一个 Integer[] 并填充它,而 ArrayUtils.toPrimitive() 将分配一个 int[] 并对输入进行解包。 - atanamir

85

Java 8通过流的方式为我们提供了一种简单的方法...

使用集合的stream()函数然后映射到整数,你将获得一个IntStream。使用IntStream,我们可以调用toArray(),得到int []

int [] ints = list.stream().mapToInt(Integer::intValue).toArray();

转换为 int 数组

转换为IntStream


62

使用:

int[] toIntArray(List<Integer> list)  {
    int[] ret = new int[list.size()];
    int i = 0;
    for (Integer e : list)
        ret[i++] = e;
    return ret;
}

在你的代码中进行这个微小的更改,是为了避免昂贵的列表索引(因为一个List不一定是一个ArrayList,它可以是一个链表,而随机访问是昂贵的)。


5
该方法的参数是List,不是所有实现都具有快速随机访问(例如ArrayList)。 - Arjan
1
@Hugh:差异不在于如何访问数组,而在于如何访问列表。每次访问时,list.get(i) 执行边界检查等操作。我不知道新的解决方案是否真的更好,但迈克至少这么说,所以也许确实如此。编辑:我忘记了 Java 允许索引到链表(我习惯于 C++ 的 std::list,它不允许这样做)。所以 arjantop 说的关于非 ArrayList 的内容是正确的;即使没有边界检查,索引也不一定快。 - Lajnold
@Lajnold 现代JIT中的ArrayList边界检查是免费的。然而,我更喜欢Java更遵循STL并且只实现真正有意义的方法(LinkedList::get(int)可能会任意缓慢)。 - maaartinus
@Nearoo 不,这是胡说八道。你想要将实现与接口分离并不意味着你可以破坏接口,例如,在大多数重要操作中缺乏时间保证。幸运的是,几乎没有人使用LinkedList,因此假设List :: get(int)是一个常数时间操作是相当安全的。 - maaartinus
List-接口是所谓的“抽象数据类型”。它没有特定的实现方式。有许多不同的数据类型用于不同的目的,以及许多具有不同性能的实现。ArrayList在O(n)中添加新元素,并具有查找O(1)。链表则相反。为什么要将接口限制为只有一个?如果您只想支持特定的运行时,可以将“ArrayList”写为参数类型。否则,运行时可以在注释中说明。使用方法是由客户端决定的,而不是类的责任。 - Nearoo
显示剩余4条评论

14

这是一个使用Java 8的单行代码:

public int[] toIntArray(List<Integer> intList){
    return intList.stream().mapToInt(Integer::intValue).toArray();
}

12
如果你只是将一个Integer映射到一个int,那么你应该考虑使用并行处理,因为你的映射逻辑不依赖于其作用域外的任何变量。
int[] arr = list.parallelStream().mapToInt(Integer::intValue).toArray();

请注意

并行不一定比串行操作快,尽管如果你有足够的数据和处理器核心,它可能会更快。虽然聚合操作使您更容易实现并行性,但仍需您自己确定应用程序是否适合并行处理。


将整数映射到其原始形式有两种方法:

  1. Via a ToIntFunction.

    mapToInt(Integer::intValue)
    
  2. Via explicit unboxing with lambda expression.

    mapToInt(i -> i.intValue())
    
  3. Via implicit (auto-) unboxing with lambda expression.

    mapToInt(i -> i)
    

给定一个包含null值的列表。
List<Integer> list = Arrays.asList(1, 2, null, 4, 5);

以下是处理 null 的三个选项:

  1. Filter out the null values before mapping.

    int[] arr = list.parallelStream().filter(Objects::nonNull).mapToInt(Integer::intValue).toArray();
    
  2. Map the null values to a default value.

    int[] arr = list.parallelStream().map(i -> i == null ? -1 : i).mapToInt(Integer::intValue).toArray();
    
  3. Handle null inside the lambda expression.

    int[] arr = list.parallelStream().mapToInt(i -> i == null ? -1 : i.intValue()).toArray();
    

1
请注意,mapToInt(Integer::intValue)mapToInt(i -> i.intValue())是严格相同的(表示完全相同的方法调用的两种方式),而且所有三者在本质上都是相同的(具有相同的字节码)。 - AndrewF
这太棒了,谢谢分享! - kzs

9
这个简单的循环是正确的!没有错误。
  int[] integers = new int[myList.size()];
  for (int i = 0; i < integers.length; i++) {
      integers[i] = myList.get(i);
  }

3
“有效”并不等同于“理想”,性能问题可以视为错误。 List#get(int)不能保证是常量时间操作,就像您可能假设的那样,因此不应用于迭代。相反,请使用专为此用例设计的迭代器。可以使用Java 5+ foreach循环或调用 List#iterator() 并使用迭代器进行操作。此外,在此循环期间列表的大小可能会更改,导致 IndexOutOfBoundsException 或不完整的数组。许多迭代器实现都有充分记录的策略来处理此情况。 - AndrewF
1
远比 Java 流(streams)混乱的狗屎要好。 - garryp
这与问题中的内容完全相同。问题是“有没有更好的方法来做这件事?”这个回答如何回答这个问题?大多数其他答案都试图回答这个问题。 - Peter Mortensen

8

我注意到有几种使用for循环的方法,但实际上你甚至不需要在循环内部放置任何内容。我提到这一点只是因为原始问题试图找到更简洁的代码。

int[] toArray(List<Integer> list) {
    int[] ret = new int[ list.size() ];
    int i = 0;
    for( Iterator<Integer> it = list.iterator();
         it.hasNext();
         ret[i++] = it.next() );
    return ret;
}

如果Java允许在for循环中像C++那样进行多个声明,我们可以更进一步,例如:for(int i = 0, Iterator it...。但最终(这只是我的观点),如果你要用一个辅助函数或方法来帮助你做事情,只需设置它并忘记它即可。它可以是一行代码或十行代码;如果你永远不再查看它,你将不会知道其中的区别。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接