在Java 8中,为什么Arrays没有给出Iterable的forEach方法?

时间:2016-02-20 02:37:26

标签: java arrays foreach java-8 javac

我必须在这里遗漏一些东西。

在Java 5中,引入了"for-each loop" statement (also called the enhanced for loop)。它似乎主要是为了迭代集合而引入的。任何实现Iterable接口的集合(或容器)类都可以使用“for-each循环”进行迭代。也许出于历史原因,Java阵列没有实现Iterable接口。但由于数组无处不在,javac会接受在数组上使用for-each循环(生成相当于传统for循环的字节码)。

在Java 8中,forEach method作为默认方法添加到Iterable接口。这使得将lambda表达式传递给集合(迭代时)成为可能(例如list.forEach(System.out::println))。但同样,阵列不喜欢这种治疗方法。 (我知道有解决方法)。

有没有技术上的原因为什么javac无法增强以接受forEach中的数组,就像它在增强的for循环中接受它们一样?似乎可以在不要求数组实现Iterable的情况下生成代码。我是天真的吗?

这对于语言的新手来说尤其重要,因为他们的语法很容易,他们更自然地使用数组。切换到列表并使用Arrays.asList(1, 2, 3)

是不自然的

2 个答案:

答案 0 :(得分:43)

Java语言和JVM中有许多特殊情况。数组有一个API,但它几乎看不到。就像声明数组有:

  • implements Cloneable, Serializable
  • public final int length
  • public T[] clone()其中T是数组的组件类型

但是,这些声明在任何地方的任何源代码中都不可见。有关说明,请参阅JLS 4.10.3JLS 10.7CloneableSerializable通过反射可见,并通过调用

返回
Object[].class.getInterfaces()

也许令人惊讶的是,length字段和clone()方法不能反映出来。 length字段根本不是字段;使用它会变成一个特殊的arraylength字节码。对clone()的调用会导致实际的虚方法调用,但如果接收者是数组类型,则由JVM专门处理。

值得注意的是,数组类没有实现Iterable接口。

当在Java SE 5中添加了增强型for循环(" for-each")时,它支持右侧表达式的两种不同情况:Iterable或者数组类型(JLS 14.14.2)。原因是增强型for语句完全不同地处理Iterable个实例和数组。 JLS的那一部分提供了全面的处理,但更简单地说,情况如下。

对于Iterable<T> iterable,代码

for (T t : iterable) {
    <loop body>
}

的语法糖
for (Iterator<T> iterator = iterable.iterator(); iterator.hasNext(); ) {
    t = iterator.next();
    <loop body>
}

对于数组T[],代码

for (T t : array) {
    <loop body>
}

的语法糖
int len = array.length;
for (int i = 0; i < len; i++) {
    t = array[i];
    <loop body>
}

现在,为什么这样做?数组肯定可以实现Iterable,因为它们已经实现了其他接口。编译器也可以合成由数组支持的Iterator实现。 (有先例。编译器已经合成了自动添加到每个values()类的静态valueOf()enum方法,如JLS 8.9.3中所述。)

但是数组是一个非常低级的构造,并且通过int值访问数组预计是非常便宜的操作。从0运行循环索引到数组的长度是非常惯用的,每次递增1。数组上的增强for循环就是这样。如果使用Iterable协议实现了数组上的增强for循环,我想大多数人会发现循环数组涉及初始方法调用和内存分配(创建{{1})会让人不高兴。 }),然后每循环迭代两次方法调用。

因此,当在Java 8中将默认方法添加到Iterator时,这根本不会影响数组。

正如其他人所说,如果您有Iterableintlong或参考类型的数组,则可以将其转换为流使用其中一个double来电。这样就可以访问Arrays.stream()map()filter()

但是,如果Java语言中的特殊情况和数组的JVM被真正的结构替换(以及修复一些其他与数组相关的问题,例如差错),那将会很不错处理2维阵列,2 ^ 31长度限制,等等。这是&#34; Arrays 2.0&#34;的主题。调查由约翰罗斯领导。请参阅John在JVMLS 2012上的演讲(videoslides)。与此讨论相关的想法包括引入数组的实际接口,允许库插入元素访问,支持切片和复制等其他操作。

请注意,所有这些都是调查和未来的工作。在撰写本文时(2016-02-23),在任何版本的Java路线图中都没有提供这些数组增强功能。

答案 1 :(得分:7)

假设特殊代码将被添加到java编译器中以处理forEach。 然后可以提出许多类似的问题。 为什么我们不能写myArray.fill(0)?还是myArray.copyOfRange(from, to)?还是myArray.sort()myArray.binarySearch()myArray.stream()?实际上Arrays中的每个静态方法 接口可以转换为&#34;数组类的相应方法&#34;。为什么JDK开发人员会停下来 myArray.forEach()?但请注意,每个此类方法不仅必须添加到classlib规范中, 但是Java语言规范更加稳定和保守。这也意味着不仅如此 这些方法的实现将成为规范的一部分,但也会成为java.util.function.Consumer之类的类 应该在JLS中明确提到(这是提议的forEach方法的参数)。 另请注意,新增消费者需要加入 标准库,如FloatConsumerByteConsumer等,用于相应的数组类型。 目前,JLS很少引用java.lang包之外的类型(有一些值得注意的例外) 比如java.util.Iterator)。这意味着一些稳定层。对于Java语言,提议的更改过于激烈。

另请注意,目前我们有一种方法可以直接调用数组(哪种实现方式不同) 来自java.lang.Object):它的clone()方法。它实际上将一些脏的部分添加到javac甚至JVM中 因为它必须在任何地方特别处理。这会导致错误(例如,在Java 8 JDK-8056051中错误地处理了方法引用)。在javac中添加更多类似的复杂性可能会引入 甚至更类似的错误。

这种功能可能会在不久的将来作为其中的一部分实施 Arrays 2.0 initiative。 我们的想法是为数组引入一些超类,这些数组将位于类库中,所以 只需编写普通的java代码而不调整javac / JVM就可以添加新的方法。然而, 这也是一个非常难的功能,因为数组总是在Java中专门处理,并且, 据我所知,它是否会被实施以及何时实施。