将Java数组转换为可迭代对象

176

我有一个原始类型的数组,例如int,int[] foo。它可能是小型的,也可能不是。

int foo[] = {1,2,3,4,5,6,7,8,9,0};

如何最好地从中创建一个Iterable<Integer>

Iterable<Integer> fooBar = convert(foo);

注意:

请勿使用循环来回答问题(除非您能够提供编译器如何对其进行智能处理的良好解释?)

另请注意:

int a[] = {1,2,3};
List<Integer> l = Arrays.asList(a);

甚至无法编译

Type mismatch: cannot convert from List<int[]> to List<Integer>

在回答之前,请先查看为什么数组不可分配给Iterable?

另外,如果您使用某些库(例如Guava),请解释为什么这是最佳选择。(因为它来自Google不是完整的答案:P)

最后,由于似乎有一些作业与此相关,请避免发布类似作业的代码。


可能是数组的迭代器的重复问题。 - NPE
将它们添加到LinkedList中,然后只返回该Set的迭代器。 - user1181445
10个回答

144
Integer foo[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };

List<Integer> list = Arrays.asList(foo);
// or
Iterable<Integer> iterable = Arrays.asList(foo);

尽管这个方法需要使用Integer数组(而不是int数组)才能正常工作。

对于原始数据类型,您可以使用guava:

Iterable<Integer> fooBar = Ints.asList(foo);
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>15.0</version>
    <type>jar</type>
</dependency>

对于Java8与Lambda表达式:(受Jin Kwon答案的启发)

final int[] arr = { 1, 2, 3 };
final Iterable<Integer> i1 = () -> Arrays.stream(arr).iterator();
final Iterable<Integer> i2 = () -> IntStream.of(arr).iterator();
final Iterable<Integer> i3 = () -> IntStream.of(arr).boxed().iterator();

13
两个注意事项:1)他使用的是int,而不是Integer;2)List已经实现了Iterable接口,因此第三行代码是无意义的。 - maksimov
1
他需要一个可迭代对象,这就是为什么有第三行的原因。 - fmucar
5
仅翻译文本内容,不进行解释:第二行和第三行是我会说的选项 :) - fmucar
1
这不是作业的一部分,我只是试图避免为处理数组或列表内容的调试功能重复编写代码... 在寻找时我确实找到了Arrays.asList(..);,但至少Eclipse似乎认为它不会做我想要的事情(例如,它将Arrays.asList(foo)的结果推断为List<int[]>而不是List<Integer>...)我觉得这很有趣,值得提出问题... - ntg
1
在数组上循环不会慢,数组是最快的数据结构之一,因为它是一系列内存块,并且循环意味着按顺序读取内存块,与其他类似的数据对象相比永远不会慢。 - fmucar
显示剩余4条评论

50

仅供参考:

final int a[] = {1,2,3};

java.lang.Iterable<Integer> aIterable=new Iterable<Integer>() {

    public Iterator<Integer> iterator() {
       return new Iterator<Integer>() {
            private int pos=0;

            public boolean hasNext() {
               return a.length>pos;
            }

            public Integer next() {
               return a[pos++];
            }

            public void remove() {
                throw new UnsupportedOperationException("Cannot remove an element of an array.");
            }
        };
    }
};

10
在Java 8中,remove()方法已经是默认方法,会抛出UnsupportedOperationException异常,因此不再必要。只有当你想提供更好的解释信息时才需要使用该方法。 - Alex
+1 我做了类似的事情,从String创建Iterator<Character>。实现自己的Iterator似乎是避免不必要地迭代所有值以从对象类型转换为原始类型(例如通过Guava的Ints.asList())只是为了能够获得从创建的List中获取Iterator的唯一方法。 - spaaarky21
2
你是对的Alex。 Java 8添加了默认方法。 2013年,我在这里添加了这个古老的代码片段。 - Joerg Ruethschilling

36

使用Java 8,你可以做到这一点。

final int[] arr = {1, 2, 3};
final PrimitiveIterator.OfInt i1 = Arrays.stream(arr).iterator();
final PrimitiveIterator.OfInt i2 = IntStream.of(arr).iterator();
final Iterator<Integer> i3 = IntStream.of(arr).boxed().iterator();

20

Guava 提供了你想要的适配器,例如Int.asList()。与相应类中的每种基本类型等效,例如 Booleans 代表 boolean 等。

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

上面提到使用Arrays.asList的建议不起作用,即使编译通过,因为您会得到一个Iterator<int[]>而不是Iterator<Integer>。发生的情况是,您没有创建一个由数组支持的列表,而是创建了一个包含您的数组的1个元素数组列表。


请注意:链接已失效。Github链接:https://github.com/google/guava/blob/master/guava/src/com/google/common/primitives/Ints.java - lue
感谢@Passi,已修复(似乎找不到谷歌支持的链接到javadoc的方法了,所以我链接到了您提供的源代码)。 - BeeOnRope

11

在Java 8或更高版本中,Iterable是一个返回Iterator的函数式接口。因此,您可以这样做。

static Iterable<Integer> convert(int[] array) {
    return () -> Arrays.stream(array).iterator();
}
int[] array = {1, 2, 3};
Iterable<Integer> iterable = convert(array);
for (int i : iterable)
    System.out.println(i);

输出:

1
2
3

8

我也曾遇到过同样的问题,解决方法如下:

final YourType[] yourArray = ...;
return new Iterable<YourType>() {
  public Iterator<YourType> iterator() {
     return Iterators.forArray(yourArray);   // Iterators is a Google guava utility
  }
}

迭代器本身是一个惰性的UnmodifiableIterator,而这正是我所需要的。

4

你可以使用IterableOf来自Cactoos

Iterable<String> names = new IterableOf<>(
  "Scott Fitzgerald", "Fyodor Dostoyevsky"
);

然后,您可以使用 ListOf 将其转换为列表:

List<String> names = new ListOf<>(
  new IterableOf<>(
    "Scott Fitzgerald", "Fyodor Dostoyevsky"
  )
);

或者仅仅是这样:

List<String> names = new ListOf<>(
  "Scott Fitzgerald", "Fyodor Dostoyevsky"
);

4

首先,对于包装类型或非基本数据类型的数组,Arrays.asList(T...) 显然是最好的解决方案。该方法调用 Arrays 类中一个简单的私有静态 AbstractList 实现的构造函数,将给定的数组引用保存为字段,并通过覆盖所需的方法来模拟列表。

如果您可以在基本类型和包装类型之间选择数组,则在这种情况下我会使用包装类型,但当然,并不总是有用或必需的。您只有两种可能性:

1)您可以为每个原始数据类型数组创建一个带有静态方法的类 (boolean, byte, short, int, long, char, float, double),返回一个 Iterable<WrapperType>。这些方法将使用 Iterator 的匿名类 (除了 Iterable),这些类允许包含组成方法参数的引用 (例如一个 int[]) 作为字段以实现方法。

-> 这种方法具有高性能,并且节省内存 (除了新创建的方法的内存之外,即使使用 Arrays.asList() 也会以相同的方式占用内存)

2)由于数组没有方法(如您链接的一侧所述),它们也不能提供 Iterator 实例。如果您真的懒得编写新类,那么您必须使用已经存在的实现 Iterable 的类的实例,因为除了实例化 Iterable 或其子类型之外,没有其他方法。
创建现有的集合派生类实现 Iterable 的唯一方法是使用循环 (除非您使用上面描述的匿名类),或者实例化一个允许原始类型数组的 Iterable 实现类的构造函数 (因为 Object[] 不允许具有基本类型元素的数组),但据我所知,Java API 并没有这样的类。

循环的原因很容易解释:对于每个集合,您需要对象,而原始数据类型不是对象。对象比原始类型大得多,因此它们需要生成每个元素的额外数据。这意味着如果三种方法中有两种方法 (使用 Arrays.asList(T...) 或使用现有的集合) 需要聚合对象,那么您需要为 int[] 数组的每个原始值创建包装器对象。第三种方式将使用数组本身,并在匿名类中使用它,我认为由于快速性能,这是首选方式。

还有第三种策略,使用一个 Object 作为您想要使用数组或 Iterable 的方法的参数,它需要类型检查来确定参数的类型,但我不建议这样做,因为通常您需要考虑到对象并不总是具有所需的类型,并且您需要针对某些情况编写单独的代码。

总之,这是 Java 问题的 Generic Type 系统的问题,它不允许将基本类型用作通用类型,这将通过简单地使用 Arrays.asList(T...) 节省大量代码。因此,您需要为每个需要的原始类型数组编写这样的方法 (这与 C++ 程序所使用的内存没有什么区别,C++ 程序会为每个使用的类型参数创建一个单独的方法)。

2

虽然已经有类似的答案发布了,但我认为使用新的PrimitiveIterator.OfInt的原因并不清楚。一个好的解决方案是使用Java 8的PrimitiveIterator,因为它专门针对原始int类型(避免了额外的装箱/拆箱惩罚):

    int[] arr = {1,2,3};
    // If you use Iterator<Integer> here as type then you can't get the actual benefit of being able to use nextInt() later
    PrimitiveIterator.OfInt iterator = Arrays.stream(arr).iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.nextInt());
        // Use nextInt() instead of next() here to avoid extra boxing penalty
    }

参考文献:https://doc.bccnsoft.com/docs/jdk8u12-docs/api/java/util/PrimitiveIterator.OfInt.html


-2
在Java8中,IntStream流可以被封装为整数流。
public static Iterable<Integer> toIterable(int[] ints) {
    return IntStream.of(ints).boxed().collect(Collectors.toList());
}

我认为性能取决于数组的大小。


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