Каждый разработчик должен научиться использовать новые функции программирования, особенно теперь, когда 8-я версия достигла максимального пика использования. Лямбда-выражения — это новая и важная функция, включенная в Java 8. Она обеспечивает четкий и сжатый способ представления метода, который улучшает Collection библиотеки, упрощая итерацию, фильтрацию и извлечение данных. Новые возможности параллелизма улучшают производительность в многоядерных средах.
Принцип лямбда-выражений
Несколько лет назад специалистами обсуждались расширения языка Java для функционального программирования. В 2009 году стало ясно, что Java без lambdas будет выглядеть устаревшим по сравнению с другими языками программирования, поскольку современное многопроцессорное и многоядерное оборудование требует простой поддержки для распараллеливания программ. Вот зачем нужны лямбда-выражения.
В Java все коллекции имеют метод forEach, наследуемый от своего супер-интерфейса Iterable. Он существует со времен Java 5, был расширен до 8-й версии. Метод forEach выполняет итерацию по всем элементам коллекции и применяет функцию к каждому элементу.
Лямбда-выражение представляет собой анонимную функцию, обеспечивающую параметризацию поведения. Она состоит из списка параметров возврата и исключений, которые выбираются. Экземпляр лямбда-выражения Java может быть назначен любому Iterable, соответствующего определению при условии, что он является функциональным.
Важные моменты для понимания лямбда-выражения:
Синтаксических опции для списка параметров
Существует несколько синтаксических опций для списка параметров. Вот упрощенная версия синтаксиса лямбда-выражения:
Список параметров представляет собой либо список, разделенный запятыми, в круглых скобках, либо один идентификатор без скобок. Если используется скобочный список, нужно решить, будет ли прописан тип параметра для всех или если он не будет указан, тогда автоматически он определится компилятором. Вот несколько примеров лямбда-выражений Java:
(int x) -> x + 1 |
Список параметров с единственным параметром с явной спецификацией типа |
int x -> x + 1 |
Неверно, если указан тип параметра, используют круглые скобки |
(x) -> x + 1 |
Список параметров с единственным параметром без явной спецификации типа, компилятор выводит отсутствующий тип параметра из контекста |
x -> x + 1 |
Список параметров с единственным параметром без явной спецификации типа. В этом случае опускают круглые скобки |
(int x, int y) -> x + y |
Список параметров с двумя параметрами n с явной спецификацией типа |
int x, int y -> x + y |
Неверно, если указан тип параметра, то должны использоваться круглые скобки |
(x, y) -> x + y |
Список параметров с двумя параметрами без явной спецификации типа |
x, y -> x + y |
Неверно, для более чем одного параметра необходимо использовать скобки |
(x, int y) -> x + y |
Неверно, нельзя смешивать параметры с типом и без него. Либо все имеют явную спецификацию типа, либо нет |
() -> 42 |
Список может быть пустым |
Тело лямбды представляет собой либо одно выражение, либо список выражений в скобках. Вот несколько примеров лямбда-выражений Java:
() -> System.gc () |
Тело, состоящее из одиночного выражения |
(String [] args) -> (args! = null) ? args.length : 0 |
Оператор также возвращает одно выражение |
(String [] args) -> {if (args! = null) return args.length ; еще return 0 ; } |
Здесь используются фигурные скобки, потому что это инструкция, а не выражение |
(int x) -> x + 1 |
Тело, при передачи лямбда-выражений. |
(int x) -> return x + 1 |
Неверно, с возвратом начинается утверждение, и лямбда отсутствует, return должен заканчиваться точкой с запятой и находиться в скобках |
(int x) -> { return x + 1 ; } |
Правильно |
Выражение отображается только в исходном коде в точках, где существует контекст вывода, который может решить компилятор. Поэтому лямбда допускаются только в следующих местах:
- В правой части присвоений;
- в роли аргументов метода при вызове;
- в качестве значения в операторе возврата;
- в цельном значении.
Функциональные интерфейсы
8-я версия принесла мощное синтаксическое улучшение в форме выражений. Ранее обычно создавали класс для каждого случая, когда нужно было инкапсулировать единую функциональность.
Для всех функциональных интерфейсов рекомендуется иметь информативную аннотацию. Это не только четко передает его цель, но также позволяет компилятору генерировать ошибку, если аннотированный интерфейс не удовлетворяет условиям SAM. Интерфейс с Single Abstract Method является функциональным, и его реализация может рассматриваться, перед тем как использовать лямбда-выражения в Java 8.
Методы изначально не абстрактны и не учитываются, а интерфейс может по-прежнему иметь несколько методов по умолчанию. Можно наблюдать это, глядя на документацию Function. Самый простой и общий случай лямбда — это функциональный интерфейс с методом, который получает одно значение, возвращая другое. Эта функция одного аргумента представлена интерфейсом Function, который параметризуется типами аргументов и возвращаемыми значениями. Примеры-лямбда выражения Java:
- 1;
- public interface Function { … }.
Анонимный внутренний класс
В Java анонимные внутренние классы предоставляют способ реализации классов, встречающиеся в приложении только единожды. Например, в стандартном приложении Swing или JavaFX требуется несколько обработчиков событий для клавиатуры и мыши. Вместо того чтобы писать отдельный класс для каждого события, прописывают общую команду. Создавая класс на месте, где это необходимо, код становится читать намного проще.
Нельзя правильно понять, что такое лямбда выражения, не рассмотрев функциональные интерфейсы. Использование их с анонимными внутренними классами является общим шаблоном в Java. В дополнение к EventListener классам, такие интерфейсы, как Runnable, Comparator используются аналогичным образом. Поэтому функциональные интерфейсы используются конкретно с лямбда-выражениями.
Выражение анонимной функции
В основном, Lambda Expression — краткое представление анонимной функции, которая может быть передана. Таким образом, она обладает следующими свойствами:
Синтаксис выражения
Лямбда-выражения определяют объемность анонимных внутренних классов путем преобразования 5 строк кода в один оператор. Это простое горизонтальное решение разрешает «вертикальную проблему», представленную внутренними классами. Лямбда-выражение состоит из 3 частей.
Тело может быть либо отдельным выражением, либо блоком оператора. В форме выражения тело просто оценивается и возвращается. В блочной форме тело оценивается, как тело метода, а оператор return возвращает управление вызывающей стороне анонимного метода. На верхнем уровне break continue ключевые слова являются незаконными, но разрешены внутри циклов. Если тело производит результат, каждый путь управления должен возвращать что-либо или генерировать исключение.
Например:
- (int x, int y) -> x + y;
- () -> 42;
- (String s) -> {System.out.println (s); }.
Первое выражение принимает два целочисленных аргумента, названных x и y, и использует форму выражения для возврата x + y. Второе не принимает аргументов и использует форму для возврата целого числа 42. Третье выражение принимает строку и использует форму блока для печати строки на консоли и ничего не возвращает.
Защита переменных объекта от мутации
Доступ к переменной в лямбда-выражениях приведет к ошибке времени компиляции. Но это не означает, что пользователь должен отмечать каждую целевую переменную как final. В соответствии с концепцией «фактически окончательный» компилятор рассматривает каждую переменную, как окончательную, если она назначается только один раз.
Безопасно использовать такие переменные внутри lambdas, потому что компилятор будет контролировать свое состояние и запускать ошибку времени компиляции сразу после любой попытки их изменить. Этот подход должен упростить процесс выполнения лямбда-исполнения. Одной из основных целей лямбда является использование в параллельных вычислениях — это означает, что они действительно полезны, когда дело касается безопасности потоков.
Эффективная окончательная парадигма помогает во многих, но не в каждом случае. Lambdas не может изменить значение объекта из охватывающей области. Но в случае изменяемых переменных объекта состояние может быть изменено внутри лямбда-выражений. Этот код является законным, поскольку полная переменная остается, фактически, окончательной.
Захват переменной экземпляра
Выражение лямбда также может захватывать переменную экземпляра в объекте, который создает лямбда. Вот пример, который показывает процесс.
Нужно обратить внимание на ссылку this.name внутри лямбда-тела. Это фиксирует name переменную экземпляра EventConsumerImpl объекта. Можно даже изменить значение переменной экземпляра после ее захвата, и значение будет отражено внутри лямбда. Семантика this фактически является одной из областей, где Java lambdas отличается от анонимных реализаций интерфейсов. Реализация анонимного интерфейса может иметь свои собственные переменные экземпляра, на которые ссылается this ссылка. Тем не менее, лямбда не может иметь свои собственные переменные экземпляра, поэтому this всегда указывает на охватывающий объект.
Захват статической переменной Ямба-выражение Java также может захватывать статические переменные. Это неудивительно, так как статические переменные доступны везде, где есть приложение Java. Значение статической переменной также может измениться после того, как лямбда ее захватила. Класс в первую очередь служит для того чтобы показать, что лямбда может обращаться к статическим переменным.
Ссылки на методы
В случае, когда все выражения лямбда делают ссылки на методы , нужно вызвать другой метод с параметрами, переданными лямбда, реализация лямбда Java обеспечивает более короткий способ выражения вызова метода. Вот пример одного функционального интерфейса
Поскольку тело лямбда состоит только из одного утверждения, можно фактически опустить прилагаемые { } скобки. Кроме того, поскольку для этого метода существует только один параметр, можно опустить прилагаемые ( ) скобки вокруг параметра. Вот как выглядит итоговое заявление лямбда: MyPrinter myPrinter = s -> System.out.println (s);
Поскольку все тело лямбда делает, пересылает строковый параметр в System.out.println() метод, можно заменить указанное выше lambda-объявление ссылкой на метод.
Вот как выглядит ссылка на лямбда-метод: MyPrinter myPrinter = System.out :: println.
Нужно обращать особое внимание на двойные двоеточия ::. Этот сигнал компилятору Java является ссылкой на метод. Указанный метод — это то, что происходит после двойных двоеточий. Независимо от класса или объекта, который владеет ссылочным методом.
Распространенные примеры использования
Бегущая Лямбда.
В обоих случаях параметр не передается и возвращается. Runnable лямбда — выражение, которое использует формат блока, преобразует пять строк кода в одной инструкции.
Приемник Лямбда.
Выражение лямбда передается, как параметр. Целевое типирование используется в ряде контекстов, включая следующее:
Comparator класс используется для сортировки коллекций. В следующем примере выполняется сортировка ArrayList состоящего из Person объектов на основе surName. Ниже перечислены поля, включенные в Person класс. Lambda поддерживает «целевую типизацию», которая указывает тип объекта из контекста, в котором он используется. Поскольку присваивается результат Comparator определенному при помощи generic, компилятор может сделать вывод о том, что оба параметра имеют Person тип.
Рекомендации по предоставлению целевых типов
Метод является общим и абстрактным, что позволяет легко адаптироваться практически к любому лямбда. Разработчики должны изучить этот пакет перед созданием новых интерфейсов. Скачать лямбда-выражения в Java 8 pdf можно в Интернете.
В некотором классе UseFoo он принимает этот интерфейс как параметр. Если посмотреть выражение более внимательно, то можно увидите, что Foo — это не что иное, как функция, которая принимает один аргумент и дает результат. 8-я версия уже предоставляет такой интерфейс в функции. Можно полностью удалить интерфейс Foo и изменить код, прописывая выражения.
Подход лямбда-выражения можно использовать для любого подходящего интерфейса из старых библиотек. Он может использоваться для таких интерфейсов, как Runnable, Comparator и других. Однако это не означает, что пользователь должен пересматривать старшую базу кода и изменить все.
Рекомендуется избегать методов перегрузки с функциональными интерфейсами в качестве параметров, а использовать их с разными именами, чтобы избежать столкновений. Также не стоит относиться к лямбда-выражениям, как к внутренним классам, поскольку он создает новую область. Можно перезаписать локальные переменные из охватывающей области, создав новые локальные переменные с одинаковыми именами и использовать ключевое слово this внутри класса в качестве ссылки на экземпляр.
Однако лямбда-выражения работают с охватывающей областью и нельзя перезаписывать переменные из охватывающей области внутри тела лямбды. В этом случае ключевого слова это является ссылкой на ограждающий экземпляр.
Лямбда-выражения создаются короткими и понятными, желательно использовать одну строчную конструкцию вместо большого блока кода. Нужно помнить, что лямбда должна быть выражением, а не повествованием. Несмотря на сжатый синтаксис этих выражений, они должны точно выразить функциональность, которую предоставляют. Это в основном стилистический совет, который не влияет на производительность.
Однако пользователь, не должен использовать это правило «однострочной лямбды» в качестве догмы. Если у него есть две или три строки в определении лямбда, может оказаться нецелесообразным приводить их к одной строке. И также необходимо избегать указания типов параметров. Компилятор в большинстве случаев способен разрешать тип лямбда-параметров при помощи вывода типа, поэтому добавление типа к параметрам является необязательным и может быть опущено.
Синтаксис лямбда требует скобок только вокруг более одного параметра или при отсутствии какого-либо параметра. Вот почему безопасно сделать код немного короче и исключить круглые скобки, когда есть только один параметр. Такие рекомендации по составлению лямбда-выражений Java для чайников, можно найти в Интернете.
Операции скобок и возврата являются необязательными в однострочных лямбда-телах. Это означает, что их можно опустить для ясности и краткости. Очень часто лямбда-выражения просто вызывают методы, которые уже реализованы в другом месте. В этой ситуации очень полезно использовать другую функцию — ссылки на методы. Это не всегда короче, но делает код более удобочитаемым.
Лямбда-выражения прекрасно работают вместе только с функциональными интерфейсами Java 8. Нельзя использовать лямбда-выражения с интерфейсом с более чем одним абстрактным методом. Чтобы работать с подобными выражениями, нужно убедиться, что у пользователя установлена восьмая версия Java. Лямбда-выражения не работают на Java 7 и более ранних версиях.