Что такое lambda выражение java
Lambda-выражения в Java
Привет, Хабр! Представляю вашему вниманию перевод статьи «Java Lambda Expressions» автора www.programiz.com.
Введение
В этой статье, с помощью примеров, мы изучим lambda-выражения в Java, их использование с функциональными интерфейсами, параметризированными функциональными интерфейсами и Stream API.
Лямбда выражения были добавлены в Java 8. Их основная цель – повысить читабельность и уменьшить количество кода.
Но, прежде чем перейти к лямбдам, нам необходимо понимать функциональные интерфейсы.
Что же такое функциональный интерфейс?
Если интерфейс в Java содержит один и только один абстрактный метод, то он называется функциональным. Этот единственный метод определяет назначение интерфейса.
Например, интерфейс Runnable из пакета java.lang является функциональным, потому, что он содержит только один метод run().
Пример 1: объявление функционального интерфейса в java
В приведенном выше примере, интерфейс MyInterface имеет только один абстрактный метод getValue(). Значит, этот интерфейс — функциональный.
Здесь мы использовали аннотацию FunctionalInterface, которая помогает понять компилятору, что интерфейс функциональный. Следовательно, не позволяет иметь более одного абстрактного метода. Тем не менее, мы можем её опустить.
В Java 7, функциональные интерфейсы рассматривались как Single Abstract Methods (SAM). SAM обычно реализовывались с помощью анонимных классов.
Пример 2: реализация SAM с помощью анонимного класса в java
В этом примере, мы принимаем анонимный класс для вызова метода. Это помогало писать программы с меньшим количеством строк кода в Java 7. Однако, синтаксис оставался достаточно сложным и громоздким.
Java 8 расширила возможности SAM, сделав шаг вперед. Как мы знаем, функциональный интерфейс содержит только один метод, следовательно, нам не нужно указывать название метода при передаче его в качестве аргумента. Именно это и позволяет нам lambda-выражения.
Введение в лямбда-выражения
Лямбда-выражения, по сути, это анонимный класс или метод. Лямбда-выражение не выполняется само по себе. Вместо этого, оно используется для реализации метода, определенного в функциональном интерфейсе.
Как записать лямбда-выражение в Java?
В Java, лямбда-выражения имеют следующий синтаксис:
Здесь мы использовали новый оператор (->) — лямбда-оператор. Возможно, синтаксис кажется немного сложным. Давайте разберем пару примеров.
Предположим, у нас есть такой метод:
Мы можем записать его, используя лямбда, как:
Этот метод не имеет никаких параметров. Следовательно, левая часть выражения содержит пустые скобки. Правая сторона – тело лямбда-выражения, которое определяет его действие. В нашем случае, возвращается значение 3.1415.
Типы лямбда-выражений
В Java, тело лямбды может быть двух типов.
2. Блочные (многострочные)
Этот тип позволяет лямбда-выражению иметь несколько операций внутри себя. Эти операции должны быть помещены в фигурные скобки, после которых необходимо ставить точку с запятой.
Примечание: многострочные лямбда-выражения, всегда должны иметь оператор return, в отличии от однострочных.
Пример 3: лямбда-выражение
Давайте напишем Java программу, которая бы возвращала значение Pi, используя лямбда-выражение.
Как говорилось ранее, лямбда-выражение не выполняется само собой. Скорее, оно формирует реализацию абстрактного метода, объявленного в функциональном интерфейсе.
И так, для начала, нам необходимо описать функциональный интерфейс.
Лямбда-выражения с параметрами
До этого момента, мы создавали лямбда-выражения без каких-либо параметров. Однако, как и методы, лямбды могут иметь параметры.
В этом примере, переменная n внутри скобок является параметром, переданном в лямбда-выражение. Тело лямбды принимает параметр и проверяет его на четность.
Пример 4: использование лямбда-выражения с параметрами
Параметризированный функциональный интерфейс
До этого момента, мы использовали функциональные интерфейсы, которые принимали только один тип значения. Например:
Вышеупомянутый функциональный интерфейс принимает только String и возвращает String. Однако, мы можем сделать наш интерфейс универсальным, чтобы использовать с любым типом данных.
Пример 5: параметризированный интерфейс и лямбда-выражения
В этом примере, мы создали параметризированный функциональный интерфейс GenericInterface, который содержит параметризированный метод func().
Затем, внутри класса Main:
Лямбда-выражения и Stream API
В JDK8 добавлен новый пакет java.util.stream, который позволяет java-разработчикам выполнять такие операции, как поиск, фильтрация, сопоставление, объединение или манипулирование коллекциями, к примеру Lists.
Например, у нас есть поток данных (в нашем случае список строк), где каждая строка содержит название страны и ее город. Теперь мы можем обработать этот поток данных и выбрать только города Непала.
Для этого мы можем использовать комбинацию Stream API и лямбда-выражений.
Пример 6: использование лямбд в Stream API
В приведенном выше примере обратите внимание на это выражение:
Здесь мы используем такие методы, как filter(), map(), forEach() из Stream API, которые могут принимать лямбды в качестве параметра.
Также, мы можем описать собственные выражения на основе синтаксиса, описанного выше. Это позволит нам уменьшить количество строк кода.
Лямбда-выражения в Java 8
В новой версии Java 8 наконец-то появились долгожданные лямбда-выражения. Возможно, это самая важная новая возможность последней версии; они позволяют писать быстрее и делают код более ясным, а также открывают дверь в мир функционального программирования. В этой статье я расскажу, как это работает.
Java задумывалась как объектно-ориентированный язык в 90-е годы, когда объектно-ориентированное программирование было главной парадигмой в разработке приложений. Задолго до этого было объектно-ориентированное программирование, были функциональные языки программирования, такие, как Lisp и Scheme, но их преимущества не были оценены за пределами академической среды. В последнее время функциональное программирование сильно выросло в значимости, потому что оно хорошо подходит для параллельного программирования и программирования, основанного на событиях («reactive»). Это не значит, что объектная ориентированность – плохо. Наоборот, вместо этого, выигрышная стратегия – смешивать объектно-ориентированное программирование и функциональное. Это имеет смысл, даже если вам не нужна параллельность. Например, библиотеки коллекций могут получить мощное API, если язык имеет удобный синтаксис для функциональных выражений.
Главным улучшением в Java 8 является добавление поддержки функциональных программных конструкций к его объектно-ориентированной основе. В этой статье я продемонстрирую основной синтаксис и как использовать его в нескольких важных контекстах. Ключевые моменты понятия лямбды:
Зачем нужны лямбды?
Лямбда-выражение представляет собой блок кода, который можно передать в другое место, поэтому он может быть выполнен позже, один или несколько раз. Прежде чем углубляться в синтаксис (и любопытное название), давайте сделаем шаг назад и увидим, где вы использовали аналогичные блоки кода в Java до этого.
Ключевым моментом является то, что метод run содержит код, который нужно выполнить в отдельном потоке.
Рассмотрим сортировку с использованием пользовательского компаратора. Если вы хотите отсортировать строки по длине, а не по умолчанию, вы можете передать объект Comparator в метод sort :
В качестве другого примера отложенного выполнения рассмотрим коллбэк для кнопки. Вы помещаете действие обратного вызова в метод класса, реализующего интерфейс слушателя, создаете экземпляр, и регистрируете экземпляр. Это настолько распространенный сценарий, что многие программисты используют синтаксис «анонимный экземпляр анонимного класса»:
Поскольку Java 8 позиционирует JavaFX в качестве преемника инструментария Swing GUI, я использую JavaFX в этих примерах. Детали не имеют значения. В каждой библиотеке пользовательского интерфейса, будь то Swing, JavaFX или Android, вы передаете кнопке некоторый код, который вы хотите запустить, когда кнопка нажата.
Во всех трех примерах вы видели один и тот же подход. Блок кода кому-то передавался — пулу потоков, методу сортировки или кнопке. Этот код вызывался некоторое время спустя.
До сих пор передача кода не была простой в Java. Вы не могли просто передать блоки кода куда угодно. Java является объектно-ориентированным языком, так что вы должны были создать объект, принадлежащий к классу, у которого есть метод с нужным кодом.
В других языках можно работать с блоками кода непосредственно. Проектировщики Java сопротивлялись добавлению этой функции в течение длительного времени. В конце концов, большая сила Java в ее простоте и последовательности. Язык может стать крайне беспорядочным, если будет включать в себя все функции, которые дают чуть более краткий код. Тем не менее, в тех других языках, это не просто легче порождать поток или зарегистрировать обработчик кнопки щелчка; многие их API проще, более последовательны и мощные. В Java, можно было бы написать подобные интерфейсы, которые принимают объекты классов, реализующих определенную функцию, но такие API было бы неудобно использовать.
В последнее время вопрос был не в том, расширять Java для функционального программирования или нет, а как это сделать. Потребовалось несколько лет экспериментов, прежде чем выяснилось, что это хорошо подходит для Java. В следующем разделе вы увидите, как можно работать с блоками кода в Java 8.
Синтаксис лямбда-выражений
Рассмотрим предыдущий пример сортировки еще раз. Мы передаем код, который проверяет, какая строка короче. Мы вычисляем
Вы только что видели ваше первое лямбда-выражение! Такое выражение является просто блоком кода вместе со спецификацией любых переменных, которые должны быть переданы в код.
Почему такое название? Много лет назад, когда еще не было никаких компьютеров, логик Алонзо Чёрч хотел формализовать, что значит для математической функции быть эффективно вычисляемой. (Любопытно, что есть функции, которые, как известно, существуют, но никто не знает, как вычислить их значения.) Он использовал греческую букву лямбда (λ), чтобы отметить параметры. Если бы он знал о Java API, он написал бы что-то не сильно похожее на то, что вы видели, скорее всего.
Почему буква λ? Разве Чёрч использовал все буквы алфавита? На самом деле, почтенный труд Principia Mathematica использует символ ˆ для обозначения свободных переменных, которые вдохновили Чёрча использовать заглавную лямбда (Λ) для параметров. Но, в конце концов, он переключился на строчной вариант буквы. С тех пор, выражение с переменными параметрами было названо «лямбда-выражение».
Если лямбда-выражение не имеет параметров, вы все равно ставите пустые скобки, так же, как с методом без параметров:
Если типы параметров лямбда-выражения можно вывести, можно опустить их. Например,
Здесь компилятор может сделать вывод, что firstStr и secondStr должны быть строками, потому что лямбда-выражение присваивается компаратору строк. (Мы посмотрим на это присваивание повнимательнее позже.)
Если метод имеет один параметр выводимого типа, вы можете даже опустить скобки:
Вы можете добавить аннотации или модификатор final к параметрам лямбды таким же образом, как и для параметров метода:
Вы никогда не указываете тип результата лямбда-выражения. Это всегда выясняется из контекста. Например, выражение
Функциональные интерфейсы
Вы можете поставить лямбда-выражение всякий раз, когда ожидается объект интерфейса с одним абстрактным методом. Такой интерфейс называется функциональным интерфейсом.
Это преобразование в интерфейсы – это то, что делает лямбда-выражения настолько мощными. Синтаксис короткий и простой. Вот еще один пример:
Этот код очень легко читать.
Наконец, заметим, что checked исключения могут возникнуть при преобразовании лямбды в экземпляр функционального интерфейса. Если тело лямбда-выражения может бросить checked исключение, это исключение должно быть объявлено в абстрактном методе целевого интерфейса. Например, следующее было бы ошибкой:
Поскольку Runnable.run не может бросить исключение, это присваивание является некорректным. Чтобы исправить ошибку, у вас есть два варианта. Вы можете поймать исключение в теле лямбда-выражения. Или вы можете присвоить лямбду интерфейсу, один абстрактный метод которого может бросить исключение. Например, метод call из интерфейса Callable может бросить любое исключение. Таким образом, вы можете присвоить лямбду Callable (если добавить return null ).
Ссылки на методы
В качестве другого примера, предположим, что вы хотите отсортировать строки независимо от регистра букв. Вы можете написать такой код:
Как вы можете видеть из этих примеров оператор :: отделяет имя метода от имени объекта или класса. Есть три основных варианта:
Ссылки на конструктор
Но это неудовлетворительно. Пользователь хочет массив кнопок, а не объектов. Библиотека потоков решает эту проблему за счет ссылок на конструкторы. Передайте Button[]::new методу toArray :
Метод toArray вызывает этот конструктор для получения массива нужного типа. Затем он заполняет и возвращает массив.
Область действия переменной
Часто вы хотели бы иметь возможность получить доступ к переменным из охватывающего метода или класса в лямбда-выражении. Рассмотрим следующий пример:
Если подумать хорошенько, то не очевидно, что здесь происходит. Код лямбда-выражения может выполниться гораздо позже вызова repeatText и переменные параметров уже будут потеряны. Как же переменные text и count остаются доступными?
Чтобы понять, что происходит, мы должны уточнить наши представления о лямбда-выражениях. Лямбда-выражение имеет три компонента:
Техническим термином для блока кода вместе со значениями свободных переменных является замыкание. Если кто-то злорадствует, что их язык поддерживает замыкания, будьте уверены, что Java также их поддерживает. В Java лямбда-выражения являются замыканиями. На самом деле, внутренние классы были замыканиями все это время. Java 8 предоставляет нам замыкания с привлекательным синтаксисом.
Как вы видели, лямбда-выражение может захватить значение переменной в охватывающей области. В Java, чтобы убедиться, что захватили значение корректно, есть важное ограничение. В лямбда-выражении можно ссылаться только на переменные, значения которых не меняются. Например, следующий код является неправильным:
Существует причина для этого ограничения. Изменяющиеся переменные в лямбда-выражениях не потокобезопасны. Рассмотрим последовательность параллельных задач, каждая из которых обновляет общий счетчик.
Если бы этот код был правомерным, это было бы не слишком хорошо. Приращение matchCount++ неатомарно, и нет никакого способа узнать, что произойдет, если несколько потоков выполнят этот код одновременно.
Внутренние классы могут также захватывать значения из охватывающей области. До Java 8 внутренние классы могли иметь доступ только к локальным final переменным. Это правило теперь ослаблено для соответствия правилу для лямбда-выражений. Внутренний класс может получить доступ к любой эффективно final локальной переменной; то есть, к любой переменной, значение которой не изменяется.
Не рассчитывайте, что компилятор выявит все параллельные ошибки доступа. Запрет на модификацию имеет место только для локальных переменных. Если matchCount – переменная экземпляра или статическая переменная из охватывающего класса, то никакой ошибки не будет, хотя результат так же не определен.
Кроме того, совершенно законно изменять разделяемый объект, хоть это и не очень надежно. Например,
Существуют безопасные механизмы подсчета и сбора значений одновременно. Вы можете использовать потоки для сбора значений с определенными свойствами. В других ситуациях вы можете использовать потокобезопасные счетчики и коллекции.
Как и с внутренними классами, есть обходное решение, которое позволяет лямбда-выражению обновить счетчик в локальной охватывающей области видимости. Используйте массив длиной 1, вроде этого:
Конечно, такой код не потокобезопасный. Для обратного вызова кнопки это не имеет значения, но в целом, вы должны подумать дважды, прежде чем использовать этот трюк.
Тело лямбда-выражения имеет ту же область видимости, что и вложенный блок. Здесь применяются те же самые правила для конфликтов имен. Нельзя объявить параметр или локальную переменную в лямбде, которые имеют то же имя, что и локальная переменная.
Внутри метода вы не можете иметь две локальные переменные с тем же именем. Таким образом, вы не можете объявить такие переменные также и в лямбда-выражении. При использовании ключевого слова this в лямбда-выражении вы ссылаетесь на параметр this метода, который создает лямбду. Рассмотрим, например, следующий код
Методы по умолчанию
Многие языки программирования интегрируют функциональные выражения с их библиотеками коллекций. Это часто приводит к коду, который короче и проще для понимания, чем эквивалент, использующий циклы. Например, рассмотрим цикл:
Рассмотрим такой интерфейс:
Что произойдет, если вы создадите класс, реализующий оба?
Теперь предположим, что Naming интерфейс не содержит реализацию по умолчанию для getFirstName :
Если ни один интерфейс не обеспечивает реализацию по умолчанию для общего метода, то мы находимся в пре-Java 8 ситуации и нет никакого конфликта. У класса реализации есть две возможности: реализовать метод или оставить его нереализованным. В последнем случае класс сам является абстрактным.
Мы только что обсудили конфликты имен между двумя интерфейсами. Теперь рассмотрим класс, расширяющий суперкласс и реализующий интерфейс, наследуя тот же метод от обоих. Например, предположим, что Person является классом и Student определяется как:
Лямбда-выражения
Введение в лямбда-выражения
Среди новшеств, которые были привнесены в язык Java с выходом JDK 8, особняком стоят лямбда-выражения. Лямбда представляет набор инструкций, которые можно выделить в отдельную переменную и затем многократно вызвать в различных местах программы.
По факту лямбда-выражения являются в некотором роде сокращенной формой внутренних анонимных классов, которые ранее применялись в Java. В частности, предыдущий пример мы можем переписать следующим образом:
Чтобы объявить и использовать лямбда-выражение, основная программа разбивается на ряд этапов:
Определение ссылки на функциональный интерфейс:
Причем параметры лямбда-выражения соответствуют параметрам единственного метода интерфейса Operationable, а результат соответствует возвращаемому результату метода интерфейса. При этом нам не надо использовать ключевое слово return для возврата результата из лямбда-выражения.
Использование лямбда-выражения в виде вызова метода интерфейса:
Так как в лямбда-выражении определена операция сложения параметров, результатом метода будет сумма чисел 10 и 20.
При этом для одного функционального интерфейса мы можем определить множество лямбда-выражений. Например:
Отложенное выполнение
Одним из ключевых моментов в использовании лямбд является отложенное выполнение (deferred execution). То есть мы определяем в одном месте программы лямбда-выражение и затем можем его вызывать при необходимости неопределенное количество раз в различных частях программы. Отложенное выполнение может потребоваться, к примеру, в следующих случаях:
Выполнение кода отдельном потоке
Выполнение одного и того же кода несколько раз
Выполнение кода в результате какого-то события
Выполнение кода только в том случае, когда он действительно необходим и если он необходим
Передача параметров в лямбда-выражение
Параметры лямбда-выражения должны соответствовать по типу параметрам метода из функционального интерфейса. При написании самого лямбда-выражения тип параметров писать необязательно, хотя в принципе это можно сделать, например:
Если метод не принимает никаких параметров, то пишутся пустые скобки, например:
Если метод принимает только один параметр, то скобки можно опустить:
Терминальные лямбда-выражения
Выше мы рассмотрели лямбда-выражения, которые возвращают определенное значение. Но также могут быть и терминальные лямбды, которые не возвращают никакого значения. Например:
Лямбды и локальные переменные
Переменные x и y объявлены на уровне класса, и в лямбда-выражении мы их можем получить и даже изменить. Так, в данном случае после выполнения выражения изменяется значение переменной x.
Более того, мы не сможем изменить значение переменной, которая используется в лямбда-выражении, вне этого выражения. То есть даже если такая переменная не объявлена как константа, по сути она является константой.
Блоки кода в лямбда-выражениях
Существуют два типа лямбда-выражений: однострочное выражение и блок кода. Примеры однострочных выражений демонстрировались выше. Блочные выражения обрамляются фигурными скобками. В блочных лямбда-выражениях можно использовать внутренние вложенные блоки, циклы, конструкции if, switch, создавать переменные и т.д. Если блочное лямбда-выражение должно возвращать значение, то явным образом применяется оператор return :
Обобщенный функциональный интерфейс
Функциональный интерфейс может быть обобщенным, однако в лямбда-выражении использование обобщений не допускается. В этом случае нам надо типизировать объект интерфейса определенным типом, который потом будет применяться в лямбда-выражении. Например:
Таким образом, при объявлении лямбда-выражения ему уже известно, какой тип параметры будут представлять и какой тип они будут возвращать.
Разбираем лямбда-выражения в Java
От переводчика: LambdaMetafactory, пожалуй, один из самых недооценённых механизмов Java 8. Мы открыли его для себя совсем недавно, но уже по достоинству оценили его возможности. В версии 7.0 фреймворка CUBA улучшена производительность за счет отказа от рефлективных вызовов в пользу генерации лямбда выражений. Одно из применений этого механизма в нашем фреймворке — привязка обработчиков событий приложения по аннотациям, часто встречающаяся задача, аналог EventListener из Spring. Мы считаем, что знание принципов работы LambdaFactory может быть полезно во многих Java приложениях, и спешим поделиться с вами этим переводом.
В этой статье мы покажем несколько малоизвестных хитростей при работе с лямбда-выражениями в Java 8 и ограничения этих выражений. Целевая аудитория статьи — senior Java разработчики, исследователи и разработчики инструментария. Будет использоваться только публичный Java API без com.sun.* и других внутренних классов, поэтому код переносим между разными реализациями JVM.
Короткое предисловие
Например, у нас есть следующий код:
Этот код будет преобразован компилятором Java во что-то похожее на:
Инструкция invokedynamic может быть примерно представлена как вот такой Java код:
В Oracle JRE 8 metafactory динамически генерирует Java класс, используя ObjectWeb Asm, который и создает класс-реализацию функционального интерфейса. К созданному классу могут быть добавлены дополнительные поля, если лямбда-выражение захватывает внешние переменные. Этот похоже на анонимные классы Java, но есть следующие отличия:
Реализация metafactory зависит от вендора JVM и от версии
Конечно же, инструкция invokedynamic используется не только для лямбда-выражений в Java. В основном, она применяется при выполнении динамических языков в среде JVM. Движок Nashorn для исполнения JavaScript, который встроен в Java, интенсивно использует эту инструкцию.
Далее мы сфокусируемся на классе LambdaMetafactory и его возможностях. Следующий
раздел этой статьи исходит из предположения, что вы отлично понимаете как работают методы metafactory и что такое MethodHandle
Трюки с лямбда-выражениями
В этом разделе мы покажем, как строить динамические конструкции из лямбд для использования в ежедневных задачах.
Проверяемые исключения и лямбды
Не секрет, что все функциональные интерфейсы, которые есть в Java, не поддерживают проверяемые исключения. Преимущества проверяемых исключений перед обычными — это очень давний (и до сих пор горячий) спор.
А что, если вам нужно использовать код с проверяемыми исключениями внутри лямбда-выражений в сочетании с Java Streams? Например, нужно преобразовать список строк в список URL как здесь:
В конструкторе URL(String) объявлено проверяемое исключение, таким образом, он не может быть использован напрямую в виде ссылки на метод в классе Functiion.
Вы скажете: «Нет, возможно, если использовать вот такую хитрость»:
Это грязный хак. И вот почему:
Проблема может быть решена более «легальным» способом, с использованием знания следующих фактов:
Решение — обернуть метод Callable.call в метод без секции throws :
Сначала нам нужно объявить функциональный интерфейс, в котором нет секции throws
но который сможет делегировать вызов к Callable.call :
Третье — напишем вспомогательный метод, который вызывает Callable.call без объявления исключений:
Теперь можно переписать stream без всяких проблем с проверяемыми исключениями:
Этот код скомпилируется без проблем, потому что в callUnchecked нет объявления проверяемых исключений. Более того, вызов этого метода может быть заинлайнен при помощи мономорфного инлайн кэширования, потому что это только один класс во всей JVM, который реализует интерфейс SilentOnvoker
Если реализация Callable.call выкинет исключение во время выполнения, то оно будет перехвачено вызывающей функцией без всяких проблем:
Несмотря на возможности этого метода, нужно всегда помнить про следующую рекомендацию:
Скрывайте проверяемые исключения при помощи callUnchecked только если уверены, что вызываемый код не выкинет никаких исключений
Следующий пример показывает пример такого подхода:
Полная реализация этого метода находится здесь, это часть проекта с открытым кодом SNAMP.
Работаем с Getters и Setters
Этот раздел будет полезен тем, кто пишет сериализацию/десериализацию для различных форматов данных, таких как JSON, Thrift и т.д. Более того, он может быть довольно полезен, если ваш код сильно полагается на рефлексию для Getters и Setters в JavaBeans.
Первый шаг: необходимо создать кэш для getters и setters. Класс Method из Reflection API представляет реальный getter или setter и используется в качестве ключа.
Значение кэша — динамически сконструированный функциональный интерфейс для определенного getter’а или setter’а.
Во-вторых, создадим фабричные методы, которые создают экземпляр функционального интерфейса на основе ссылок на getter или setter.
Автоматическое приведение типов между аргументами типа Object в функциональных интерфейсах (после стирания типов) и реальными типами аргументов и возвращамого значения достигается при помощи разницы между samMethodType и instantiatedMethodType (третий и пятый аргументы метода metafactory, соответственно). Тип созданного экземпляра метода — это и есть специализация метода, который предоставляет реализацию лямбда-выражения.
В-третьих, создадим фасад для этих фабрик с поддержкой кэширования:
А теперь — время тестировать код:
Этот подход с закэшированными getters и setters можно эффективно использовать в библиотеках для сериализации/десериализации (таких, как Jackson), которые используют getters и setters во время сериализации и десериализации.
Вызовы функциональных интерфейсов с динамически сгенерированными реализациями с использованием LambdaMetaFactory значительно быстрее, чем вызовы через Java Reflection API
Полную версию кода можно найти здесь, это часть библиотеки SNAMP.
Ограничения и баги
В этом разделе мы рассмотрим некоторые баги и ограничения, связанные с лямбда-выражениями в компиляторе Java и JVM. Все эти ограничения можно воспроизвести в OpenJDK и Oracle JDK с javac версии 1.8.0_131 для Windows и Linux.
Создание лямбда-выражений из обработчиков методов
Этот код эквивалентен:
Но что, если мы заменим обработчик метода, который указывает на getValue на обработчик, который представляет getter поля:
Этот код должен, ожидаемо, работать, потому что findGetter возвращает обработчик, который указывает на getter поля и у него правильная сигнатура. Но, если вы запустите этот код, то увидите следующее исключение:
Что интересно, getter для поля работает нормально, если будем использовать MethodHandleProxies:
Нужно отметить, что MethodHandleProxies — не очень хороший способ для динамического создания лямбда-выражений, потому что этот класс просто оборачивает MethodHandle в прокси-класс и делегирует вызов InvocationHandler.invoke методу MethodHandle.invokeWithArguments. Этот подход использует Java Reflection и работает очень медленно.
Как было показано ранее, не все обработчики методов могут быть использованы для создания лямбда-выражений во время выполнения кода.
Только несколько типов обработчиков методов могут быть использованы для динамического создания лямбда-выражений
Generic исключения
Но, если мы заменим лямбда-выражение анонимным классом, то код скомпилируется:
Вывод типов для generic исключений не работет корректно в сочетании с лямбда-выражениями
Ограничения типов параметризации
Второе ограничение приводит к разному поведению кода во время компиляции и во время выполнения, когда происходит связываение с лямбда-выражения. Эту разницу можно продемонстрировать, используя следующий код:
Этот код абсолютно корректный и успешно компилируется. Класс MutableInteger удовлетворяет ограничениям обобщенного типа T:
Но код упадет с исключением во время выполнения:
Этот пример демонстрирует некорректный вывод типов в компиляторе и среде исполнения.
Обработка нескольких ограничений типов generic параметров в сочетании с использованием лямбда-выражений во время компиляции и выполнения — неконсистентна