在Java 8中,为什么数组没有给出可迭代的forEach方法?

时间:2022-04-26 07:40:59

I must be missing something here.

我一定是漏掉了什么。

In Java 5, the "for-each loop" statement (also called the enhanced for loop) was introduced. It appears that it was introduced mainly to iterate through Collections. Any collection (or container) class that implements the Iterable interface is eligible for iteration using the "for-each loop". Perhaps for historic reasons, the Java arrays did not implement the Iterable interface. But since arrays were/are ubiquitous, javac would accept the use of for-each loop on arrays (generating bytecode equivalent to a traditional for loop).

在Java 5中,引入了“for-each循环”语句(也称为增强的for- loop)。它似乎主要是通过集合来迭代的。实现可迭代接口的任何集合(或容器)类都可以使用“for-each循环”进行迭代。也许出于历史原因,Java数组没有实现可迭代接口。但是由于数组是/是普遍存在的,javac将接受对数组使用for-each循环(生成等价于传统for循环的字节码)。

In Java 8, the forEach method was added to the Iterable interface as a default method. This made passing lambda expressions to collections (while iterating) possible (e.g. list.forEach(System.out::println)). But again, arrays don't enjoy this treatment. (I understand that there are workarounds).

在Java 8中,forEach方法作为默认方法添加到可迭代接口中。这使得将lambda表达式传递给集合(同时迭代)成为可能(例如list.forEach(System.out: println))。但同样,数组不喜欢这种处理方式。(我知道有变通的办法。)

Are there technical reasons why javac couldn't be enhanced to accept arrays in forEach, just like it accepts them in the enhanced for loop? It appears that code generation would be possible without requiring that arrays implement Iterable. Am I being naive?

为什么不能增强javac以接受forEach中的数组,就像它在增强的for循环中接受数组一样?看起来代码生成是可能的,不需要数组实现Iterable。我太天真吗?

This is especially important for a newcomer to the language who rather naturally uses arrays because of their syntactical ease. It's hardly natural to switch to Lists and use Arrays.asList(1, 2, 3).

对于语言的新手来说,这一点尤为重要,因为他们很容易使用数组。切换到列表和使用数组是很不自然的。asList(1、2、3)。

2 个解决方案

#1


36  

There are a bunch of special cases in the Java language and in the JVM for arrays. Arrays have an API, but it's barely visible. It is as if arrays are declared to have:

在Java语言和JVM中有很多用于数组的特殊情况。数组有API,但几乎不可见。就好像数组被声明为:

  • implements Cloneable, Serializable
  • 实现了可克隆,可串行化的
  • public final int length
  • 公众最终int长度
  • public T[] clone() where T is the array's component type
  • 公共T[] clone(),其中T是数组的组件类型

However, these declarations aren't visible in any source code anywhere. See JLS 4.10.3 and JLS 10.7 for explanations. Cloneable and Serializable are visible via reflection, and are returned by a call to

但是,这些声明在任何地方的源代码中都不可见。有关解释,请参见JLS 4.10.3和JLS 10.7。可以通过反射看到Cloneable和Serializable,并通过调用返回它们

Object[].class.getInterfaces()

Perhaps surprisingly, the length field and the clone() method aren't visible reflectively. The length field isn't a field at all; using it turns into a special arraylength bytecode. A call to clone() results in an actual virtual method call, but if the receiver is an array type, this is handled specially by the JVM.

也许令人惊讶的是,length字段和clone()方法的反射性并不明显。长度场根本不是一个场;使用它会变成一个特殊的arraylength字节码。对clone()的调用会导致实际的虚拟方法调用,但如果接收方是数组类型,则由JVM特别处理。

Notably, though, array classes do not implement the Iterable interface.

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

When the enhanced-for loop ("for-each") was added in Java SE 5, it supported two different cases for the right-hand-side expression: an Iterable or an array type (JLS 14.14.2). The reason is that Iterable instances and arrays are handled completely differently by the enhanced-for statement. That section of the JLS gives the full treatment, but put more simply, the situation is as follows.

当Java SE 5中添加增强for- for-each循环(“for-each”)时,它支持右边表达式的两种不同情况:可迭代的或数组类型(JLS 14.2)。原因是可迭代的实例和数组被增强的-for语句以完全不同的方式处理。JLS的那个部分提供了完整的处理,但更简单地说,情况如下。

For an Iterable<T> iterable, the code

对于可迭代的 Iterable,代码。

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

is syntactic sugar for

是语法糖

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

For an array T[], the code

对于数组T[],代码

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

is syntactic sugar for

是语法糖

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

Now, why was it done this way? It would certainly be possible for arrays to implement Iterable, since they implement other interfaces already. It would also be possible for the compiler to synthesize an Iterator implementation that's backed by an array. (There is precedent for this. The compiler already synthesizes the static values() and valueOf() methods that are automatically added to every enum class, as described in JLS 8.9.3.)

为什么这样做呢?数组当然可以实现可迭代,因为它们已经实现了其他接口。编译器也可以合成一个由数组支持的迭代器实现。(这是有先例的。编译器已经合成了静态值()和valueOf()方法,这些方法会自动添加到每个enum类中,如JLS 8.9.3所述。

But arrays are a very low-level construct, and accessing an array by an int value is expected to be extremely inexpensive operation. It's quite idiomatic to run a loop index from 0 to an array's length, incrementing by one each time. The enhanced-for loop on an array does exactly that. If the enhanced-for loop over an array were implemented using the Iterable protocol, I think most people would be unpleasantly surprised to discover that looping over an array involved an initial method call and memory allocation (creating the Iterator), followed by two method calls per loop iteration.

但是数组是一个非常低级的构造,通过int值访问数组被认为是非常廉价的操作。将循环索引从0运行到数组的长度,每次递增一个,这是相当惯用的做法。在数组中增加for循环的功能正是这样做的。如果使用可迭代协议实现数组上的增强for循环,我认为大多数人会惊讶地发现,在数组上的循环涉及初始方法调用和内存分配(创建迭代器),然后在每个循环迭代中进行两次方法调用。

So when default methods were added to Iterable in Java 8, this didn't affect arrays at all.

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

As others have noted, if you have an array of int, long, double, or of reference type, it's possible to turn this into a stream using one of the Arrays.stream() calls. This provides access to map(), filter(), forEach(), etc.

正如其他人所注意到的,如果您有一个int、long、double或reference类型的数组,那么可以使用其中一个array .stream()调用将其转换为流。这提供了对map()、filter()、forEach()等的访问。

It would be nice, though, if the special cases in the Java language and JVM for arrays were replaced by real constructs (along with fixing a bunch of other array-related problems, such as poor handling of 2+ dimensional arrays, the 2^31 length limitation, and so forth). This is the subject of the "Arrays 2.0" investigation being led by John Rose. See John's talk at JVMLS 2012 (video, slides). The ideas relevant to this discussion include introduction of an actual interface for arrays, to allow libraries to interpose element access, to support additional operations such as slicing and copying, and so forth.

就好了,不过,如果特殊情况在Java语言和JVM取而代之的是真正的对数组构造(以及其他固定一群array-related问题,例如贫穷的处理2 +维数组、2 ^ 31长度限制,等等)。这是由John Rose领导的“数组2.0”调查的主题。参见John在JVMLS 2012年的演讲(视频,幻灯片)。与此讨论相关的思想包括为数组引入一个实际的接口,允许库插入元素访问,支持其他操作,如切片和复制,等等。

Note that all of this is investigation and future work. There is nothing from these array enhancements that is committed in the Java roadmap for any release, as of this writing (2016-02-23).

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

#2


5  

Suppose the special code will be added into java compiler to handle forEach. Then many similar questions could be asked. Why we cannot write myArray.fill(0)? Or myArray.copyOfRange(from, to)? Or myArray.sort()? myArray.binarySearch()? myArray.stream()? Practically every static method in Arrays interface could be converted into the corresponding method of the "array class". Why should JDK developers stop on myArray.forEach()? Note however that every such method must be added not only into classlib specification, but into Java Language Specification which is far more stable and conservative. Also this would mean that not only the implementation of such methods would become part of specification, but also classes like java.util.function.Consumer should be explicitly mentioned in JLS (which is the argument of proposed forEach method). Also note that new consumers would be necessary to add to the standard library like FloatConsumer, ByteConsumer, etc. for the corresponding array types. Currently the JLS rarely refers to the types outside of java.lang package (with some notable exceptions like java.util.Iterator). This implies some stability layer. The proposed change is too drastic for Java language.

假设特殊的代码将被添加到java编译器中,以处理forEach。然后可以问许多类似的问题。为什么不能写myarray。fill(0)?或myArray。copyOfRange(,)?或myArray.sort()?myArray.binarySearch()?myArray.stream()?实际上,数组接口中的每个静态方法都可以转换为相应的“数组类”方法。为什么JDK开发人员要在myArray.forEach()上停下来?但是请注意,每个这样的方法不仅必须添加到classlib规范中,而且还必须添加到Java语言规范中,后者更加稳定和保守。这也意味着,不仅这些方法的实现将成为规范的一部分,而且还将成为java.util.function之类的类。在JLS中应该明确地提到使用者(这是每个方法的参数)。还要注意,对于相应的数组类型,需要向标准库添加新的使用者,如FloatConsumer、ByteConsumer等。目前,JLS很少引用java之外的类型。lang包(有些例外,如java.util.Iterator)。这意味着有一个稳定层。对于Java语言来说,提议的更改太激烈了。

Also note that currently we have one method which could be called for arrays directly (and which implementation differs from the java.lang.Object): it's clone() method. It actually adds some dirty parts into javac and even JVM as it must be handled specially everywhere. This causes bugs (e.g. method references were incorrectly handled in Java 8 JDK-8056051). Adding more similar complexity into javac may introduce even more similar bugs.

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

Such feature will probably be implemented in some not so near future as a part of Arrays 2.0 initiative. The idea is to introduce some superclass for arrays which will be located in class library, so new methods could be added just by writing normal java code without tweaking javac/JVM. However, this is also very hard feature as arrays are always treated specially in Java, and, as far as I know it's unknown yet whether it will be implemented and when.

这种特性很可能在不久的将来作为数组2.0计划的一部分实现。其思想是为数组引入一些超类,这些超类将位于类库中,因此只需编写普通的java代码就可以添加新方法,而无需对javac/JVM进行调整。但是,这也是一个非常困难的特性,因为数组总是在Java中特别处理,而且,据我所知,还不知道它是否会实现以及何时实现。

#1


36  

There are a bunch of special cases in the Java language and in the JVM for arrays. Arrays have an API, but it's barely visible. It is as if arrays are declared to have:

在Java语言和JVM中有很多用于数组的特殊情况。数组有API,但几乎不可见。就好像数组被声明为:

  • implements Cloneable, Serializable
  • 实现了可克隆,可串行化的
  • public final int length
  • 公众最终int长度
  • public T[] clone() where T is the array's component type
  • 公共T[] clone(),其中T是数组的组件类型

However, these declarations aren't visible in any source code anywhere. See JLS 4.10.3 and JLS 10.7 for explanations. Cloneable and Serializable are visible via reflection, and are returned by a call to

但是,这些声明在任何地方的源代码中都不可见。有关解释,请参见JLS 4.10.3和JLS 10.7。可以通过反射看到Cloneable和Serializable,并通过调用返回它们

Object[].class.getInterfaces()

Perhaps surprisingly, the length field and the clone() method aren't visible reflectively. The length field isn't a field at all; using it turns into a special arraylength bytecode. A call to clone() results in an actual virtual method call, but if the receiver is an array type, this is handled specially by the JVM.

也许令人惊讶的是,length字段和clone()方法的反射性并不明显。长度场根本不是一个场;使用它会变成一个特殊的arraylength字节码。对clone()的调用会导致实际的虚拟方法调用,但如果接收方是数组类型,则由JVM特别处理。

Notably, though, array classes do not implement the Iterable interface.

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

When the enhanced-for loop ("for-each") was added in Java SE 5, it supported two different cases for the right-hand-side expression: an Iterable or an array type (JLS 14.14.2). The reason is that Iterable instances and arrays are handled completely differently by the enhanced-for statement. That section of the JLS gives the full treatment, but put more simply, the situation is as follows.

当Java SE 5中添加增强for- for-each循环(“for-each”)时,它支持右边表达式的两种不同情况:可迭代的或数组类型(JLS 14.2)。原因是可迭代的实例和数组被增强的-for语句以完全不同的方式处理。JLS的那个部分提供了完整的处理,但更简单地说,情况如下。

For an Iterable<T> iterable, the code

对于可迭代的 Iterable,代码。

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

is syntactic sugar for

是语法糖

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

For an array T[], the code

对于数组T[],代码

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

is syntactic sugar for

是语法糖

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

Now, why was it done this way? It would certainly be possible for arrays to implement Iterable, since they implement other interfaces already. It would also be possible for the compiler to synthesize an Iterator implementation that's backed by an array. (There is precedent for this. The compiler already synthesizes the static values() and valueOf() methods that are automatically added to every enum class, as described in JLS 8.9.3.)

为什么这样做呢?数组当然可以实现可迭代,因为它们已经实现了其他接口。编译器也可以合成一个由数组支持的迭代器实现。(这是有先例的。编译器已经合成了静态值()和valueOf()方法,这些方法会自动添加到每个enum类中,如JLS 8.9.3所述。

But arrays are a very low-level construct, and accessing an array by an int value is expected to be extremely inexpensive operation. It's quite idiomatic to run a loop index from 0 to an array's length, incrementing by one each time. The enhanced-for loop on an array does exactly that. If the enhanced-for loop over an array were implemented using the Iterable protocol, I think most people would be unpleasantly surprised to discover that looping over an array involved an initial method call and memory allocation (creating the Iterator), followed by two method calls per loop iteration.

但是数组是一个非常低级的构造,通过int值访问数组被认为是非常廉价的操作。将循环索引从0运行到数组的长度,每次递增一个,这是相当惯用的做法。在数组中增加for循环的功能正是这样做的。如果使用可迭代协议实现数组上的增强for循环,我认为大多数人会惊讶地发现,在数组上的循环涉及初始方法调用和内存分配(创建迭代器),然后在每个循环迭代中进行两次方法调用。

So when default methods were added to Iterable in Java 8, this didn't affect arrays at all.

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

As others have noted, if you have an array of int, long, double, or of reference type, it's possible to turn this into a stream using one of the Arrays.stream() calls. This provides access to map(), filter(), forEach(), etc.

正如其他人所注意到的,如果您有一个int、long、double或reference类型的数组,那么可以使用其中一个array .stream()调用将其转换为流。这提供了对map()、filter()、forEach()等的访问。

It would be nice, though, if the special cases in the Java language and JVM for arrays were replaced by real constructs (along with fixing a bunch of other array-related problems, such as poor handling of 2+ dimensional arrays, the 2^31 length limitation, and so forth). This is the subject of the "Arrays 2.0" investigation being led by John Rose. See John's talk at JVMLS 2012 (video, slides). The ideas relevant to this discussion include introduction of an actual interface for arrays, to allow libraries to interpose element access, to support additional operations such as slicing and copying, and so forth.

就好了,不过,如果特殊情况在Java语言和JVM取而代之的是真正的对数组构造(以及其他固定一群array-related问题,例如贫穷的处理2 +维数组、2 ^ 31长度限制,等等)。这是由John Rose领导的“数组2.0”调查的主题。参见John在JVMLS 2012年的演讲(视频,幻灯片)。与此讨论相关的思想包括为数组引入一个实际的接口,允许库插入元素访问,支持其他操作,如切片和复制,等等。

Note that all of this is investigation and future work. There is nothing from these array enhancements that is committed in the Java roadmap for any release, as of this writing (2016-02-23).

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

#2


5  

Suppose the special code will be added into java compiler to handle forEach. Then many similar questions could be asked. Why we cannot write myArray.fill(0)? Or myArray.copyOfRange(from, to)? Or myArray.sort()? myArray.binarySearch()? myArray.stream()? Practically every static method in Arrays interface could be converted into the corresponding method of the "array class". Why should JDK developers stop on myArray.forEach()? Note however that every such method must be added not only into classlib specification, but into Java Language Specification which is far more stable and conservative. Also this would mean that not only the implementation of such methods would become part of specification, but also classes like java.util.function.Consumer should be explicitly mentioned in JLS (which is the argument of proposed forEach method). Also note that new consumers would be necessary to add to the standard library like FloatConsumer, ByteConsumer, etc. for the corresponding array types. Currently the JLS rarely refers to the types outside of java.lang package (with some notable exceptions like java.util.Iterator). This implies some stability layer. The proposed change is too drastic for Java language.

假设特殊的代码将被添加到java编译器中,以处理forEach。然后可以问许多类似的问题。为什么不能写myarray。fill(0)?或myArray。copyOfRange(,)?或myArray.sort()?myArray.binarySearch()?myArray.stream()?实际上,数组接口中的每个静态方法都可以转换为相应的“数组类”方法。为什么JDK开发人员要在myArray.forEach()上停下来?但是请注意,每个这样的方法不仅必须添加到classlib规范中,而且还必须添加到Java语言规范中,后者更加稳定和保守。这也意味着,不仅这些方法的实现将成为规范的一部分,而且还将成为java.util.function之类的类。在JLS中应该明确地提到使用者(这是每个方法的参数)。还要注意,对于相应的数组类型,需要向标准库添加新的使用者,如FloatConsumer、ByteConsumer等。目前,JLS很少引用java之外的类型。lang包(有些例外,如java.util.Iterator)。这意味着有一个稳定层。对于Java语言来说,提议的更改太激烈了。

Also note that currently we have one method which could be called for arrays directly (and which implementation differs from the java.lang.Object): it's clone() method. It actually adds some dirty parts into javac and even JVM as it must be handled specially everywhere. This causes bugs (e.g. method references were incorrectly handled in Java 8 JDK-8056051). Adding more similar complexity into javac may introduce even more similar bugs.

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

Such feature will probably be implemented in some not so near future as a part of Arrays 2.0 initiative. The idea is to introduce some superclass for arrays which will be located in class library, so new methods could be added just by writing normal java code without tweaking javac/JVM. However, this is also very hard feature as arrays are always treated specially in Java, and, as far as I know it's unknown yet whether it will be implemented and when.

这种特性很可能在不久的将来作为数组2.0计划的一部分实现。其思想是为数组引入一些超类,这些超类将位于类库中,因此只需编写普通的java代码就可以添加新方法,而无需对javac/JVM进行调整。但是,这也是一个非常困难的特性,因为数组总是在Java中特别处理,而且,据我所知,还不知道它是否会实现以及何时实现。