在Java 8中,为什么数组没有被赋予迭代的forEach方法?

2022-08-31 17:02:40

我一定错过了一些东西。

在Java 5中,引入了“for-each循环”语句(也称为增强的for循环)。它似乎主要是为了通过集合进行迭代而引入。任何实现 Iterable 接口的集合(或容器)类都有资格使用“for-each 循环”进行迭代。也许由于历史原因,Java数组没有实现该接口。但是由于数组是/无处不在的,因此将接受在数组上使用for-each循环(生成与传统for循环等效的字节码)。Iterablejavac

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

是否有技术原因不能增强以接受 中的数组,就像它在增强的 for 循环中接受数组一样?看起来可以在不需要数组实现的情况下生成代码。我是不是太天真了?javacforEachIterable

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


答案 1

在Java语言和数组的JVM中有一堆特殊情况。数组有一个 API,但它几乎不可见。就好像数组被声明为具有:

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

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

Object[].class.getInterfaces()

也许令人惊讶的是,场和方法在反射上是不可见的。该字段根本不是字段;使用它会变成一个特殊的字节码。对的调用会导致实际的虚拟方法调用,但如果接收方是数组类型,则 JVM 会特别处理此调用。lengthclone()lengtharraylengthclone()

但是,值得注意的是,数组类不实现该接口。Iterable

当在 Java SE 5 中添加增强型 for 循环(“for-each”)时,它支持右侧表达式的两种不同情况:一种或一种数组类型 (JLS 14.14.2)。原因是实例和数组的处理方式与增强型语句完全不同。JLS的该部分给出了完整的处理,但更简单地说,情况如下。IterableIterable

对于 ,代码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>
}

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

但是数组是一种非常低级的结构,通过值访问数组应该是非常便宜的操作。将循环索引从运行到数组的长度是非常惯用的,每次递增一。数组上的增强型 for 循环正是这样做的。如果数组上的增强型循环是使用该协议实现的,我想大多数人都会不愉快地惊讶地发现,循环访问数组涉及初始方法调用和内存分配(创建),然后每次循环迭代两次方法调用。int0IterableIterator

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

正如其他人所指出的,如果您有一个 、 、 或引用类型的数组,则可以使用其中一个调用将其转换为流。这提供了对 、 、 等 的访问。intlongdoubleArrays.stream()map()filter()forEach()

不过,如果 Java 语言和数组 JVM 中的特殊情况被真正的构造所取代(同时修复一堆其他与数组相关的问题,例如 2+ 维数组的处理不当、2^31 的长度限制等),那就太好了。这是John Rose领导的“Arrays 2.0”调查的主题。参见 John 在 JVMLS 2012 上的演讲(视频幻灯片)。与此讨论相关的想法包括引入数组的实际接口,以允许库插入元素访问,支持其他操作,如切片和复制等。

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


答案 2

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

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

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