为啥数组不能分配给Iterable?

Posted

技术标签:

【中文标题】为啥数组不能分配给Iterable?【英文标题】:Why is an array not assignable to Iterable?为什么数组不能分配给Iterable? 【发布时间】:2010-11-12 16:53:41 【问题描述】:

使用 Java5 我们可以编写:

Foo[] foos = ...
for (Foo foo : foos) 

或者只是在 for 循环中使用 Iterable。这非常方便。

但是,您不能像这样为可迭代编写通用方法:

public void bar(Iterable<Foo> foos)  .. 

并用数组调用它,因为它不是 Iterable:

Foo[] foos =  .. ;
bar(foos);  // compile time error 

我想知道这个设计决定背后的原因。

【问题讨论】:

Arrays.asList 我想已经足够好了 这是一个哲学问题 在 Java 5+ 中处理数组的一个很好的理由是可变参数方法。 @Torsten:是的,但如果你将它传递给一个接受 Iterable 的方法,你可能不会做任何改变。 实际上,Arrays.asList 不够好,因为它不适用于原始类型的数组。通用迭代(装箱)原始类型元素的唯一内置方法是使用反射,使用java.lang.reflect.Array,但它的性能很弱。但是,如果需要,您可以编写自己的迭代器(或 List 实现!)来包装原始类型的数组。 【参考方案1】:

不幸的是,数组不是“class-足够”。他们没有实现Iterable 接口。

虽然数组现在是实现 Clonable 和 Serializable 的对象,但我相信数组不是正常意义上的对象,并且不实现接口。

您可以在 for-each 循环中使用它们的原因是 Sun 为数组添加了一些语法糖(这是一种特殊情况)。

由于数组在 Java 1 中最初是作为“几乎是对象”的,因此在 Java 中使它们成为真正的对象将过于剧烈。

【讨论】:

不过,for-each 循环还是有糖的,那么为什么 Iterable 不能有糖呢? @mmyers:for-each 中使用的糖是 compile-time 糖。这比 VM 糖要容易得多。话虽如此,.NET 数组在这方面要好得多…… 数组可以实现接口。它们实现了CloneableSerializable 接口。 java 数组在所有意义上都是对象。请删除那一点错误信息。他们只是没有实现 Iterable。 一个数组就是一个对象。它支持 useful :P 方法,例如 wait()、wait(n)、wait(n,m)、notify()、notifyAll()、finalize(),toString() 的一个毫无意义的实现唯一有用的方法是 getClass()。【参考方案2】:

数组可以实现接口(@98​​7654321@ 和java.io.Serializable)。那么为什么不Iterable?我猜Iterable 强制添加iterator 方法,而数组不实现方法。 char[] 甚至不会覆盖 toString。无论如何,应该认为引用数组不太理想 - 使用Lists。作为 dfa cmets,Arrays.asList 会明确地为您进行转换。

(话虽如此,您可以在数组上调用clone。)

【讨论】:

> “...和数组不实现方法。”我认为这是另一个哲学问题;数组从来都不是原始类型,Java 哲学认为“一切都是对象(原始类型除外)”。那么,为什么数组不实现方法,即使从一开始就希望使用数组进行大量操作。哦,没错,在泛型作为一个遗憾的事后诸葛亮出现之前,数组是唯一的强类型集合。 如果您有一个数组中的数据,您可能正在执行低级别、性能关键的工作,例如处理从流中读取的字节[]。正如@Gareth 所说,无法迭代数组可能源于 Java 泛型不支持将原语作为类型参数。 @FatuHoku 建议泛型是一个遗憾的事后诸葛亮是不正确的。泛型的可取性总是受到赞赏。数组不是原语(我没有说它们是),但它们是低级的。您想要对数组做的一件事就是将它们用作类向量结构的实现细节。 Iterator&lt;T&gt; 也需要remove(T),尽管允许抛出UnsupportedOperationException 数组确实实现了方法:它们实现了java.lang.Object的所有方法。【参考方案3】:

数组应该支持Iterable,但它们不支持,原因与 .NET 数组不支持允许按位置进行只读随机访问的接口相同(没有定义为标准的接口)。基本上,框架中经常有烦人的小漏洞,不值得任何人花时间去修复。如果我们能以某种最佳方式自行修复它们并不重要,但我们通常做不到。

更新:为了公平起见,我提到 .NET 数组不支持支持按位置随机访问的接口(另请参阅我的评论)。但是在 .NET 4.5 中,已经定义了确切的接口,并且由数组和 List&lt;T&gt; 类支持:

IReadOnlyList<int> a = new[] 1, 2, 3, 4;
IReadOnlyList<int> b = new List<int>  1, 2, 3, 4 ;

一切还不是很完美,因为可变列表接口IList&lt;T&gt;没有继承IReadOnlyList&lt;T&gt;

IList<int> c = new List<int>  1, 2, 3, 4 ;
IReadOnlyList<int> d = c; // error

这样的更改可能存在向后兼容性问题。

如果在较新版本的 Java 中类似的事情有任何进展,我很想知道 cmets! :)

【讨论】:

.NET 数组实现 IList 接口 @Aphid - 我说的是 readonly 随机访问。 IList&lt;T&gt; 公开修改操作。如果IList&lt;T&gt; 继承了IReadonlyList&lt;T&gt; 接口之类的东西,那就太好了,它只有CountT this[int] 并继承了IEnumerable&lt;T&gt;(它已经支持只读枚举)。另一个很棒的事情是用于获取逆序枚举器的接口,Reverse 扩展方法可以查询它(就像Count 扩展方法查询ICollection 以优化自身一样。) 是的,如果事情是这样设计的,那就更好了。 IList 接口确实定义了 IsReadOnly 和 IsFixedSize 属性,它们由数组适当地实现。不过,这总是让我觉得这是一种非常糟糕的做法,因为它不提供编译时检查您给出的列表是否实际上是只读的,而且我很少看到检查这些属性的代码。 .NET 中的数组从 .NET 1.1 开始实现 IListICollection,从 .NET 2.0 开始实现 IList&lt;T&gt;ICollection&lt;T&gt;。这是 Java 远远落后于竞争对手的又一个例子。 @TomGillen:我对IList 的最大问题是它没有提供更多可查询的属性。我想说一个合适的集合应该包括 IsUpdateable、IsResizable、IsReadOnly、IsFixedSize 和 ExistingElementsAreImmutable。引用的代码是否可以在不进行类型转换的情况下修改列表的问题与包含对不应修改的列表的引用的代码是否可以安全地直接与外部代码共享该引用的问题是分开的,或者是否它可以安全地假设列表的某些方面永远不会改变。【参考方案4】:

数组是一个对象,但它的项目可能不是。数组可能包含像 int 这样的原始类型,Iterable 无法处理。至少我是这么认为的。

【讨论】:

这意味着为了支持Iterable 接口,原始数组必须专门用于使用包装类。不过,这些都不是什么大不了的事,因为无论如何类型参数都是假的。 这不会阻止 Object 数组实现 Iterable。它也不会阻止原始数组为包装类型实现 Iterable。 自动装箱可以解决这个问题 我认为这是正确的原因。在泛型支持原始类型(例如List&lt;int&gt; 而不是List&lt;Integer&gt; 等)之前,它不会对原始类型的数组令人满意。可以使用包装器进行 hack,但会损失性能 - 更重要的是 - 如果完成此 hack,它将阻止将来在 Java 中正确实现它(例如,int[].iterator() 将永远被锁定以返回 Iterator&lt;Integer&gt;而不是Iterator&lt;int&gt;)。也许,即将到来的 Java 值类型 + 泛型专业化(项目 valhalla)将使数组实现 Iterable【参考方案5】:

编译器实际上将数组上的for each 转换为带有计数器变量的简单for 循环。

编译如下

public void doArrayForEach() 
    int[] ints = new int[5];

    for(int i : ints) 
        System.out.println(i);
    

然后反编译.class文件产生

public void doArrayForEach() 
    int[] ints = new int[5];
    int[] var2 = ints;
    int var3 = ints.length;

    for(int var4 = 0; var4 < var3; ++var4) 
        int i = var2[var4];
        System.out.println(i);
    

【讨论】:

以上是关于为啥数组不能分配给Iterable?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能将从 Firestore 获取的值分配给 Swift 中的数组?

参数类型“List<Categories>”不能分配给参数类型“Iterable<Categories>”

参数类型“Iterable<Future<dynamic>>”不能分配给参数类型“Iterable<Future<news_item>>”

为啥不能成功分配数组?

为啥不能将匿名方法分配给 var?

为啥自定义子查询类型不能分配给 GraphQLObjectType?