Подтвердить что ты не робот

В Java 8 почему массивы не получили метод forEach для Iterable?

Мне, должно быть, что-то не хватает.

В Java 5 был введен оператор "for-each loop" (также называемый расширенным циклом). Похоже, что он был введен в основном для итерации через подборки. Любой класс коллекции (или контейнера), который реализует интерфейс Iterable, имеет право на итерацию, используя цикл for for each. Возможно, по историческим причинам массивы Java не реализовали интерфейс Iterable. Но поскольку массивы были/повсеместны, javac согласился бы использовать цикл for для каждого цикла на массивах (генерируя байт-код, эквивалентный традиционному циклу).

В Java 8 метод forEach был добавлен в интерфейс Iterable как метод по умолчанию. Это сделало возможным передавать лямбда-выражения в коллекции (при повторении) (например, list.forEach(System.out::println)). Но опять же, массивы не пользуются этим обращением. (Я понимаю, что есть обходные пути).

Существуют ли технические причины, по которым javac не может быть расширен, чтобы принимать массивы в forEach, так же как он принимает их в расширенном цикле? Похоже, что генерация кода возможна, не требуя реализации массивов Iterable. Я наивна?

Это особенно важно для новичков на языке, который скорее использует массивы из-за их синтаксической легкости. Слишком естественно переключиться на Списки и использовать Arrays.asList(1, 2, 3).

4b9b3361

Ответ 1

В языке Java и в JVM для массивов имеется множество специальных случаев. Массивы имеют API, но они едва заметны. Как будто массивы объявлены как:

  • implements Cloneable, Serializable
  • public final int length
  • public T[] clone() где T - тип компонента массива

Однако эти объявления нигде не отображаются ни в каком исходном коде. Подробнее см. JLS 4.10.3 и JLS 10.7. Cloneable и Serializable видны через отражение и возвращаются вызовом

Object[].class.getInterfaces()

Возможно, удивительно, что поле length и метод clone() не отражаются. Поле length не является полем; использование его превращается в специальный arraylength байт-код. Вызов clone() приводит к фактическому вызову виртуального метода, но если получатель является типом массива, это обрабатывается специально JVM.

Примечательно, что классы массивов не реализуют интерфейс Iterable.

Когда в Java SE 5 был добавлен цикл расширенного цикла ( "для каждого" ), он поддерживал два разных случая для правого выражения: a Iterable или тип массива (JLS 14.14.2). Причина в том, что экземпляры и массивы 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>
}

- синтаксический сахар для

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

Теперь, почему это было сделано так? Конечно, для массивов возможно реализовать Iterable, поскольку они уже реализуют другие интерфейсы. Также было бы возможно, чтобы компилятор синтезировал реализацию Iterator, поддерживаемую массивом. (Существует прецедент для этого. Компилятор уже синтезирует статические методы values() и valueOf(), которые автоматически добавляются к каждому классу enum, как описано в JLS 8.9.3.)

Но массивы - это очень низкоуровневая конструкция, и ожидается, что доступ к массиву с помощью значения int будет чрезвычайно недорогим. Весьма идиоматично запускать индекс цикла от 0 до длины массива, каждый раз увеличивая на единицу. Этот цикл улучшенного цикла в массиве делает именно это. Если цикл расширенного цикла над массивом был реализован с использованием протокола Iterable, я думаю, что большинство людей были бы неприятно удивлены, обнаружив, что цикл по массиву связан с начальным вызовом метода и распределением памяти (создание Iterator), затем двумя вызовами метода на итерацию цикла.

Итак, когда методы по умолчанию были добавлены в Iterable в Java 8, это вообще не затрагивало массивы.

Как отмечали другие, если у вас есть массив int, long, double или ссылочного типа, можно включить его в поток, используя один из вызовов Arrays.stream(). Это обеспечивает доступ к map(), filter(), forEach() и т.д.

Было бы неплохо, однако, если бы особые случаи на языке Java и JVM для массивов были заменены реальными конструкциями (наряду с фиксацией множества других проблем, связанных с массивом, таких как плохое управление 2 + мерными массивами, ограничение длины 2 ^ 31 и т.д.). Это предмет исследования "Arrays 2.0" под руководством Джона Роуза. См. John talk на JVMLS 2012 (video, слайды). Идеи, относящиеся к этой дискуссии, включают введение реального интерфейса для массивов, позволяющее библиотекам вставлять доступ к элементу, поддерживать дополнительные операции, такие как нарезка и копирование, и т.д.

Обратите внимание, что все это исследование и будущая работа. Ничего из этих улучшений массива, которые были зафиксированы в дорожной карте Java для любой версии, начиная с этой записи (2016-02-23). ​​

Ответ 2

Предположим, что специальный код будет добавлен в java-компилятор для обработки forEach. Тогда можно было бы задать много похожих вопросов. Почему мы не можем написать myArray.fill(0)? Или myArray.copyOfRange(from, to)? Или myArray.sort()? myArray.binarySearch()? myArray.stream()? Практически каждый статический метод в Arrays интерфейс может быть преобразован в соответствующий метод "класса массива". Почему разработчики JDK останавливаются myArray.forEach()? Обратите внимание, однако, что каждый такой метод должен быть добавлен не только в спецификации classlib, но в Спецификацию Java Language, которая намного более стабильна и консервативна. Также это будет означать, что не только реализация таких методов станет частью спецификации, но также и такие классы, как java.util.function.Consumer должно быть явно указано в JLS (что является аргументом предложенного метода forEach). Также обратите внимание, что новым потребителям необходимо будет добавить стандартная библиотека типа FloatConsumer, ByteConsumer и т.д. для соответствующих типов массивов. В настоящее время JLS редко ссылается на типы вне пакета java.lang (с некоторыми заметными исключениями как java.util.Iterator). Это подразумевает некоторый уровень устойчивости. Предлагаемое изменение слишком сильно для языка Java.

Также обратите внимание, что в настоящее время у нас есть один метод, который можно вызвать для массивов напрямую (и какая реализация отличается из метода java.lang.Object): it clone(). Это фактически добавляет некоторые грязные части в javac и даже JVM так как его нужно обрабатывать повсюду. Это вызывает ошибки (например, ссылки на методы были неправильно обработаны в Java 8 JDK-8056051). Добавление более сложной сложности в javac может ввести еще более похожие ошибки.

Такая функция, вероятно, будет реализована в ближайшее время не так как часть Активация массивов 2.0. Идея состоит в том, чтобы ввести некоторый суперкласс для массивов, который будет расположен в библиотеке классов, поэтому новые методы могут быть добавлены только путем написания нормального Java-кода без настройки javac/JVM. Однако, это также очень сложно, поскольку массивы всегда обрабатываются специально на Java, и, насколько я знаю, неизвестно, будет ли оно реализовано и когда.