openjdk.ruOpenJDK на русском

JEP 109: Enhance Core Libraries with Lambda

Улучшение основных библиотек с помощью Lambda

AuthorsStuart Marks, Mike Duigou
ОтветственныйStuart Marks
ТипFeature
ОбластьSE
СтатусClosed / Delivered
Выпуск8
Компонентcore-libs
JSR335
Обсуждениеcore dash libs dash dev at openjdk dot java dot net
ТрудоёмкостьM
ДлительностьL
БлокируетJEP 107: Bulk Data Operations for Collections
JEP 155: Concurrency Updates
Зависит отJEP 126: Lambda Expressions & Virtual Extension Methods
ОдобренBrian Goetz
Создан2011/09/28 20:00
Обновлён2025/09/01 06:29
Задача8046099

Аннотация

Улучшить API основных библиотек Java с помощью новой языковой возможности — лямбда-выражений, чтобы библиотеками было проще и удобнее пользоваться.

Цели

Основная цель — модернизировать API общих библиотек и использовать Lambda там, где это уместно. Большая часть реализаций будет добавлена в существующие классы в виде методов расширения (extension methods). Мы займёмся самыми востребованными частями библиотеки и добавим API с Lambda там, где, по нашему мнению, от этого будет больше всего пользы. В идеале Lambda должна появиться в API везде, где её ожидал бы увидеть программист, знакомый с Lambda. С другой стороны, мы хотели бы, чтобы обычные программисты натыкались в библиотеке на API с Lambda и думали: «О, здорово, здесь добавили Lambda, и теперь я могу решить свою задачу проще».

Второстепенная цель — помочь в проектировании языковой возможности Lambda: использовать Lambda в API библиотек, вызывать эти API из реального кода, оценивать результаты и передавать отзывы команде, которая разрабатывает Lambda в языке и компиляторе.

Кратко цели можно описать так:

  • добавить в существующие библиотеки новую идиому — лямбда-функции;
  • с помощью лямбда-функций сделать библиотеки полезнее и удобнее;
  • показать лучшие практики применения методов расширения;
  • показать, что привычные основные библиотеки обновляются и развиваются.

Что не является целью

Использовать Lambda везде, где это в принципе возможно, не требуется, и за пределы основных библиотек работа не выйдет. Например, клиентские библиотеки, XML и CORBA в эту работу не входят.

Это улучшение добавит в основные классы совсем немного новой функциональности — только новые способы решения привычных задач.

Конкретной цели по доле API, которые перейдут на Lambda, нет, то есть ничего вроде «мы добавим Lambda в xx % библиотеки».

Критерии успеха

Успех будет оцениваться по тому, насколько широко разработчики начнут пользоваться новыми API и возможностями. Полным успехом будет, если использование возможностей Lambda станет для Java-разработчиков стандартной идиомой работы с основными библиотеками.

Мотивация

«Проектирование языка — это проектирование библиотек.
Проектирование библиотек — это проектирование языка». — Andrew R. Koenig

В Java 8 появится новая языковая возможность Lambda. Иметь её в языке полезно, но если Lambda затронет только язык, платформа будет неполной. Платформа станет гораздо ценнее, если поддержку Lambda добавить в подходящие части API библиотеки.

За последние несколько лет появилось множество новых языков программирования, и они набирают популярность. В большинстве из них есть та или иная конструкция для блоков, замыканий или функций первого класса. Хотя Java по-прежнему язык программирования №1, распространено мнение, что он отстал от последних достижений в области языков программирования. Это широко признаётся как мотивация для самой языковой возможности Lambda. Однако нужно также подумать об API библиотек, которые поддерживают использование Lambda. У всех альтернативных языков есть библиотеки, и их API настроены так, чтобы хорошо — гладко и идиоматично — работать с замыканиями или функциональными объектами, которые предоставляет язык.

Мы ожидаем, что и в Java со временем Lambda станет широко использоваться, а вокруг этой возможности сложатся различные идиомы программирования. API библиотек Java нужно будет улучшить, чтобы наряду с нынешним, привычным способом использования они поддерживали идиоматичное использование с Lambda.

Описание

Проект состоит из двух этапов:

  1. этап обзора, на котором выявляются кандидаты для улучшения API с помощью Lambda;

  2. этап определения объёма работ и реализации, на котором кандидатам назначаются приоритеты, из них выбирается подмножество и реализуется.

Обзор кандидатов

Найти места, куда можно добавить API на основе Lambda, можно несколькими способами. Один из них — изучить другие системы с похожими языковыми возможностями, посмотреть на их библиотеки и на то, как в них используются замыкания и функции.

Возьмём, например, Ruby. Язык Ruby поддерживает множество конструкций, похожих на функции первого класса. Они полезны сами по себе: с ними программисты могут писать в функциональном стиле, создавать функции высшего порядка, составлять композиции функций и т. д. Кроме того, библиотека классов Ruby разрабатывалась вместе с языком, который поддерживает функции первого класса, поэтому многие API в библиотеке классов их используют. Мы можем просмотреть библиотеки классов Ruby и найти примеры, которые подскажут возможные улучшения библиотек Java. Вот некоторые из таких примеров из библиотеки классов Ruby:

  • У класса Integer есть методы times, upto, downto и step, которые поддерживают различные конструкции арифметических циклов. Благодаря этому такие конструкции циклов не нужно определять в самом языке.

  • У класса File есть вариант метода open, который принимает блок. Файл открывается, передаётся в блок и закрывается, когда блок завершается. Это помогает избежать утечек ресурсов и уменьшает потребность в специальных конструкциях execute-around в языке (например, в конструкции try-with-resources из Java 7).

  • В классах HTTP API есть несколько методов, принимающих блоки. Например, у класса HTTPResponse есть метод each, который вызывает блок, принимающий пары (заголовок, значение).

  • В привязке Tk для Ruby метод создания виджета new принимает блок, который выполняется в контексте только что созданного виджета. Это удобный и лаконичный способ инициализировать виджет.

Очевидно, что Lambda можно с пользой применить во многих местах и за пределами классов коллекций. Реализовывать API на основе Lambda везде, где это сделано в Ruby, не требуется, но учитывая популярность Ruby и его влияние, разумно искать первые идеи в библиотеке классов Ruby. Возможно, имеет смысл изучить и другие системы, например Groovy, Scala, Python, Clojure и Smalltalk, в поисках похожих идей для использования Lambda в API библиотек.

Другой способ — просмотреть существующий код на Java, как в самой библиотеке, так и за её пределами (например, из Qualitas Corpus), и найти кандидатов поиском по шаблонам или обобщением. Набор приёмов может быть таким:

  • Пройтись по API и добавить метод each() или forEach() ко всему, что предположительно содержит набор чего-либо или по чему можно выполнить итерацию, даже если это не коллекция. Например, в java.nio.file.Files можно добавить метод forEachLine(), который вызывает Lambda для каждой строки файла.

  • Искать места, где можно применить идиому execute-around. Например, файлы нужно закрывать после использования, блокировки — снимать после использования и т. д.

  • Искать классы, реализующие Iterable, и оценивать, пригодится ли Lambda, чтобы обратить поток управления.

  • Искать в коде циклы for и while и рассматривать возможность применить Lambda, чтобы обратить поток управления.

  • Найти абстрактные классы с одним или двумя абстрактными методами и рассмотреть возможность добавить конструкторы или фабрики, принимающие лямбды. То же рассмотреть и для интерфейсов. Шаблон — это случай, когда реализацию нужно «заполнить» переопределением; подумайте, как превратить это в передачу Lambda в качестве параметра.

Определение объёма работ и реализация

Итоговый список кандидатов, вероятно, окажется слишком длинным, чтобы реализовать его в сроки JDK 8. Кандидатам нужно будет назначить приоритеты, а затем сократить список так, чтобы он укладывался в имеющийся график при заданных ресурсах (людях), выделенных на проект. Приоритеты можно назначать по субъективной важности библиотеки, но хорошо бы подкрепить их данными об использовании. Например, может оказаться полезным провести обзор Qualitas Corpus, чтобы определить, какие части библиотеки используются чаще всего.

Вопросы проектирования языка

При разработке API с использованием Lambda следует рассмотреть следующие вопросы и передавать соответствующую информацию команде, которая проектирует Lambda в языке.

  1. Прозрачность исключений: возникает ли у вас необходимость писать лямбда-выражения, которые могут выбрасывать проверяемые исключения? Нужны ли дополнительные наборы SAM-типов с объявлениями «throws», возможно обобщёнными «throws E»? Получается ли в итоге крайне утомительно?

  2. Вариантность: вспомогательные методы для функциональных интерфейсов, вероятно, будут использовать wildcard-типы активнее, чем что-либо ещё в API; не слишком ли это утомительно? Было бы полезно иметь более удобную и менее многословную поддержку вариантности?

  3. Переопределение без упаковки: было бы полезно при проектировании функциональных интерфейсов разрешить примитивным типам или void в качестве возвращаемого типа переопределять ссылочные возвращаемые типы, например Predicate<T> <: Function<T, Boolean>?

  4. Функциональные интерфейсы на основе абстрактных классов: в предыдущем пункте об абстрактных классах — не оказывается ли полезных кандидатов так много, что было бы удобно напрямую поддержать абстрактные классы как функциональные интерфейсы, а не определять подкласс вручную?

  5. Функциональные интерфейсы с обобщённым методом: полезен ли вам функциональный интерфейс с обобщённым методом, например: interface MapFactory { <K,V> Map<K,V> make(); }. Обратите внимание: суть в том, что обобщённым является метод, а не интерфейс. Сейчас это не поддерживается; преимущество в том, что при каждом вызове можно выводить новые аргументы типа, а это может быть полезно в некоторых приложениях.

  6. Вывод типов в цепочках: в выражениях вида «foo().bar(23)» аргументы типа для «foo» выводятся независимо от части выражения «bar(23)». Мы планируем во многих случаях лучше использовать контекст при выводе типов, но всё ещё ищем опыт, который оправдал бы заход настолько далеко.

  7. Устранение неоднозначности ссылок на методы: возникает ли у вас необходимость явно устранять неоднозначность ссылок на методы (когда метод перегружен или есть конфликт статического метода и метода экземпляра) — либо записывая полную сигнатуру, либо отказываясь от ссылки и используя вместо неё лямбда-выражение?

Тестирование

Для каждого нового API, добавленного в систему, нужно будет разработать тесты. Новые API в основном независимы друг от друга, поэтому тестировать их по отдельности должно быть просто. Кроме того, улучшения API, скорее всего, будут в основном сводиться к добавлению новых методов, которые не влияют на другие API того же класса. Поэтому тестирование новых API должно быть довольно простым и не должно затрагивать существующие тесты, которые используют текущие API.

Риски и допущения

Ещё одна идиома программирования усложняет жизнь новым пользователям. Если API с Lambda будут добавляться по частям по всей системе, их стиль может начать расходиться. Вероятно, полезно сначала провести обзор и выработать единый стиль, который будет применяться во всей библиотеке.

Риск снижает то, что улучшения API в основном независимы друг от друга, поэтому объём работ можно менять без особого влияния на проект. То есть объём работ можно рассматривать как список улучшений API, упорядоченный по приоритету. В зависимости от графика проекта и состава участников в определённом месте будет проведена черта. Если по какой-либо причине эту черту нужно будет сдвинуть, это не должно повлиять на уже выполненную работу, и перепланировать придётся немного, потому что пункты в основном независимы друг от друга. По той же причине и приоритеты в списке должно быть можно пересмотреть без особых усилий, если по ходу проекта появится новая информация.

Совместное использование этих новых API с существующими может усложнить сопровождение пользовательского кода.

Зависимости

Эта работа зависит только от собственно реализации Lambda.

Даже это довольно мягкая зависимость, поскольку в API лямбда-выражения представлены как SAM. API на основе функциональных интерфейсов можно добавить ещё до того, как Lambda будет интегрирован, хотя вызывающий код тогда ещё не сможет использовать лямбда-выражения.

Влияние

  • JCP: все эти изменения затрагивают публичный API, поэтому в итоге они изменят спецификации, находящиеся под контролем JCP. Эти изменения существующих классов базовой библиотеки должны быть достаточно небольшими, чтобы не требовать отдельного JSR; они должны охватываться JSR платформы. Необходимые новые классы (функциональные интерфейсы) спроектирует и рассмотрит экспертная группа JSR 335 (EG). Разумеется, все соответствующие процедуры рецензирования должны быть соблюдены.

  • Другие компоненты JDK: реализация Lambda

  • Совместимость: изменения API вряд ли приведут к несовместимостям.

  • Документация: вероятно, понадобятся новые учебные руководства, показывающие, как использовать Lambda с новыми конструкциями библиотеки, или серия небольших примеров, иллюстрирующих типичное применение. Возможно, лямбда-выражения будет трудно заметить при просмотре javadoc, поэтому может оказаться полезным или необходимым ввести новый синтаксис javadoc или, возможно, другое форматирование вывода javadoc, чтобы особым образом выделять API, использующие Lambda. Ещё одна возможность — позаботиться о том, чтобы javadoc каждого нового API, принимающего Lambda, содержал пример его использования.