Что такое лямбда java
Перейти к содержимому

Что такое лямбда java

  • автор:

Лямбда выражения

Рассмотрим следующий код, который подсчитывает количество только компактных автомобилей:

 private int getCompactCarsNumber(Car[] cars) < int result = 0; for (Car car : cars) < if (car.getType().equals(CarTypes.COMPACT)) < result++; >> return result; > 

Предположим, добавляется еще одна задача — подсчитать количество автомобилей, у которых стоимость более 20 000:

 private int getExpensiveCarsNumber(Car[] cars) < int result = 0; for (Car car : cars) < if (car.getCost() >20000) < result++; >> return result; >

Примеры 1 и 2 по сути отличаются только одной строчкой, в которой задается критерий поиска. Изменим код таким образом, чтобы не было необходимости вводить новый метод каждый раз при добавлении нового критерия поиска:

public interface Searchable
public class CompactCarSearch implements Searchable < public boolean test(Car car) < return car.getType().equals(CarTypes.COMPACT); >>
public class ExpensiveCarSearch implements Searchable < public boolean test(Car car) < return car.getCost() >20000; > >
public class CarDemo2 < public static void main(String[] args) < Car[] cars = < new Car(CarTypes.COMPACT, 34000), new Car(CarTypes.SPORT, 44000), new Car(CarTypes.COMPACT, 14000), new Car(CarTypes.COMPACT, 10000), >; CarDemo2 carDemo = new CarDemo2(); System.out.println(carDemo.getCarsNumber(cars, new CompactCarSearch())); System.out.println(carDemo.getCarsNumber(cars, new ExpensiveCarSearch())); System.out.println(carDemo.getCarsNumber(cars, new Searchable() < @Override public boolean test(Car car) < return car.getType().equals(CarTypes.COMPACT); >>)); > private int getCarsNumber(Car[] cars, Searchable s) < int result = 0; for (Car car : cars) < if (s.test(car)) < result++; >> return result; > >

В классе CarDemo2 вместо

carDemo.getCarsNumber(cars, new Searchable() < @Override public boolean test(Car car) < return car.getType().equals(CarTypes.COMPACT); >>)

можно использовать лямбда выражение:

carDemo.getCarsNumber(cars, (Car car) -> car.getType().equals(CarTypes.COMPACT));

2. Синтаксис лямбда выражения

Общая форма записи:

(параметры) -> (тело)
(Object arg1, Object arg2) -> arg1.equals(arg2);

Лямбда выражение содержит три части: список параметров, стрелочка, тело.

Рассмотрим некоторые правила написания лямбда выражений.

    Лямбда выражение может содержать ноль и более входных параметров:

(int a1, int a2) -> < return a1 - a2; >(String s) -> < System.out.println(s); >() -> 89;
(String s) -> < System.out.println(s); >​

Можно переписать так:

(a1, a2) -> return a1 + a2; (int a1, int a2) -> return a1 + a2; () -> 42;​
a1 -> return 2 * a1​
() -> 4; (int a) -> a*6;​
() -> < System.out.println("Hi"); return 4; >(int a) -> < System.out.println(a); return a*6; >​
() -> System.out.println("Hi"); () -> < System.out.println("Hi"); return; >​

Java. Лямбда-выражения. Основные понятия. Функциональный интерфейс. Обобщенные функциональные интерфейсы и лямбда-выражения. Примеры

Java. Лямбда-выражения. Основные понятия. Функциональный интерфейс. Обобщенные функциональные интерфейсы и лямбда-выражения. Примеры

Поиск на других ресурсах:

1. Понятие о лямбда-выражении. Преимущества применения лямбда-выражений

Лямбда-выражения появились в версии JDK 8 с целью усовершенствования языка Java. Лямбда-выражения еще называют замыканиями.

К преимуществам применения лямбда-выражений в языке Java можно отнести следующие:

  • использование новых элементов, повышающих выразительность языка Java. Упрощается реализация некоторых общеупотребительных конструкций;
  • расширение возможностей библиотеки программного интерфейса (API). Сюда можно отнести упрощение параллельной обработки, улучшение работы с потоками ввода/вывода в программном интерфейсе API.

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

2. Лямбда-выражение (замыкание). Общая форма. Лямбда-оператор -> . Примеры

Реализация любого лямбда-выражения базируется на использовании двух языковых конструкций:

  • непосредственно лямбда-выражения;
  • функционального интерфейса.

Лямбда-выражение — это анонимный (безымянный) метод, который не выполняется самостоятельно, а служит реализацией метода, объявленного в функциональном интерфейсе (смотрите пункт 3). Если в программе встречается лямбда-выражение, то это приводит к созданию некоторого анонимного класса, содержащего анонимный метод, код которого определяется в лямбда-выражении.

При объявлении лямбда-выражения используется лямбда-оператор, который обозначается символами –> (стрелка). Лямбда-оператор трактуется как «становится» или «переходит». Лямбда-оператор ( –> ) разделяет лямбда-выражение на две части: левую и правую. В левой части лямбда-выражения указываются параметры. В правой части указываются действия (операторы), которые определяют код лямбда-выражения.

Код лямбда-выражения может формироваться одним из двух способов:

  • содержать одиночное выражение. В этом случае лямбда-выражение называется одиночным;
  • заключенный в фигурные скобки . Это случай, когда в правой части лямбда-оператора нужно указать несколько операторов. В этом случае лямбда-выражение называется блочным. При возврате результата в блочных лямбда-выражениях обязательно нужно указывать оператор return .

В простейшем случае, объявление блочного лямбда-выражения может быть следующим:

(list_of_parameters) -> < // Тело лямбда-выражения // . return result; >
  • list_of_parameters – список параметров, которые получает лямбда-выражение. Список параметров указывается через запятую точно также как в методе. Список параметров указывается в левой части лямбда-оператора. Параметры лямбда-выражения должны быть совместимы по типу и количеству с параметрами абстрактного метода, который объявлен в функциональном интерфейсе.

Если в лямбда-выражении содержится единственный параметр, он может быть без круглых скобок ( ) :

Возможна ситуация, когда лямбда-выражение не получает параметров. В этом случае общая форма блочного лямбда-выражения следующая

() -> < // Тело лямбда-выражения // . >

Если в лямбда-выражении используется один оператор (выражение), то фигурные скобки можно опустить:

(list_of_parameters) -> expression;
  • list_of_parameters — список параметров метода;
  • expression – выражение, которое будет вычислено при использовании лямбда-выражения.

Пример 1. Ниже приведено лямбда-выражение без параметров, которое возвращает число π :

() -> Math.PI

В вышеприведенном коде результатом лямбда-выражения служит константа PI из класса Math . Можно было написать другое решение

() -> 3.1415

Пример 2. Лямбда-выражение, которое получает два целочисленных параметра и возвращает их произведение.

(int a, int b) -> a*b

Приклад 3. Лямбда-выражение, которое по длинам трех сторон a , b , c возвращает площадь треугольника.

(double a, double b, double c ) -> < if (((a+b) return 0.0; else < double p = (a+b+c)/2; // полупериметер double s = Math.sqrt(p*(p-a)*(p-b)*(p-c)); // площадь по формуле Герона return s; > >
3. Функциональный интерфейс. Определение. Общая форма. Примеры

Функциональный интерфейс — это интерфейс, который содержит только один абстрактный метод. Функциональный интерфейс определяет только одно действие (операцию). В функциональном интерфейсе определяется целевой тип лямбда-выражения. Функциональный интерфейс еще называют SAM-типом (Single Abstract Method).

В наиболее общем случае объявления функционального интерфейса выглядит следующим образом:

interface InterfaceName < return_type MethodName(list_of_parameters); >
  • InterfaceName – имя функционального интерфейса;
  • MethodName – имя метода, который определяет назначение интерфейса;
  • return_type – тип, который возвращает метод MethodName ;
  • list_of_parameters – список параметров метода MethodName .

Если в интерфейсе объявляется два и более абстрактных метода, то этот интерфейс не считается функциональным интерфейсом.

Пример 1. Объявляется функциональный интерфейс, который определяет абстрактный метод, не получающий параметров и возвращающий целое число

// Интерфейс, который определяет метод без параметров, который возвращает целое число interface INumber < int GetNumber(); >

Код лямбда-выражения, реализующий данный интерфейс, может быть, например, следующим:

// Лямбда-выражение, реализующее интерфейс INumber INumber lambdaNumber = () -> 255;

После формирования лямбда-выражения можно вызвать метод GetNumber() интерфейса INumber()

// Использование лямбда-выражения в виде метода GetNumber() int number = lambdaNumber.GetNumber(); // number = 255 System.out.println("number color: #0000ff;">double и возвращает значение типа double

// Интерфейс, определяющий абстрактный метод, который получает 3 параметра // типа double и возвращает значение типа double. interface ICalculation < double CalcMethod(double a, double b, double c); >

Для такой сигнатуры метода можно реализовывать лямбда-выражения, которые выполняют различные операции над тремя числами, например:

  • вычисляют сумму трех чисел;
  • вычисляют максимальное (минимальное) значение среди трех чисел и т.д.

Ниже приведено построение и использование лямбда-выражения, вычисляющего сумму трех чисел

здесь parameters – список параметров метода M .

5. Решение задач на лямбда-выражения
5.1. Лямбда-выражение, которое обрабатывает три числа

Условие задачи. Создать и вызвать лямбда-выражение, которое вычисляет:

  • сумму трех чисел;
  • максимальное значение из трех чисел.

Задачу реализовать с использованием функционального интерфейса.

Решение. Текст программы следующий.

5.3. Лямбда-выражение, использующее шаблон (обобщение)

Создать и вызвать лямбда-выражение используя шаблон (обобщение). Лямбда-выражение должно находить максимум из трех цифр.

package TrainLambda; // Обобщенный (шаблонный) функциональный интерфейс. // Содержит метод, получающий 3 числа типа T и // возвращающий значение типа T. interface Max3 < T Max(T a, T b, T c); >public class TrainLambda02 < public static void main(String[] args) < // Использовение интерфейса Max3// 1. Объявить ссылку на интерфейс Max3 Max3 refInt; // 2. Создать лямбда-выражение с привязкой к типу int refInt = (a, b, c) -> < Integer max = a; if (max if (max return max; >; // 3. Вызвать лямбда выражение для трех целых чисел int res = refInt.Max(5, 7, 3); // res = 7 System. out .println( "res color: #008000;"> // 4. Создать лямбда-выражение с привязкой к типу double Max3 refDouble; refDouble = (a, b, c) -> < Double max = a; if (max if (max return max; >; // 5. Вызвать лямбда-выражение с привязкой к типу double double resMax = refDouble.Max(3.88, 2.55, 4.11); System. out .println( "resMax color: #333300;">⇑

5.4. Решение квадратного уравнения с помощью лямбда-выражения

В задаче демонстрируется использование лямбда-выражения для решения квадратного уравнения. Программа содержит следующие составляющие:

  • функциональный интерфейс Equation . В интерфейсе объявляется один метод, получающий три параметра типа double . Эти параметры являются коэффициентами a , b , c квадратного уравнения. Метод возвращает значение корней уравнения в виде экземпляра класса Solution . Если уравнение не имеет решения, то метод возвращает null ;
  • класс Solution — содержит два поля x1 , x2 типа double , которые являются корнями квадратного уравнения;
  • класс TrainLambda02 , содержащий функцию main(). В этой функции выполняется построение и использование лямбда-выражения для решения квадратного уравнения для значений a = 2, b = 8, c = 4.

Текст программы следующий:

Связанные темы

  • Лямбда-выражения для обобщенных функциональных интерфейсов
  • Передача лямбда-выражения в метод в качестве параметра. Примеры
  • Генерирование исключений в лямбда-выражениях. Примеры
  • Доступ к элементам класса в лямбда-выражениях. Захват переменных в лямбда-выражениях. Примеры

Что такое лямбда-выражения в Java

Ознакомьтесь с мощными и гибкими лямбда-выражениями в Java, которые упрощают код и делают его более читабельным!

Алексей Кодов
Автор статьи
9 июня 2023 в 16:31

Лямбда-выражения являются важным нововведением в Java, начиная с версии Java 8. Они представляют собой сокращенный синтаксис для представления объектов, реализующих функциональные интерфейсы, и позволяют писать более компактный и выразительный код.

Функциональные интерфейсы

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

@FunctionalInterface public interface MyFunction

Синтаксис лямбда-выражений

Лямбда-выражение представляет собой анонимную функцию, которую можно передать в качестве аргумента или присвоить переменной. Синтаксис лямбда-выражений включает в себя параметры, стрелку -> и тело выражения. Пример использования лямбда-выражения:

MyFunction square = x -> x * x; int result = square.apply(4); // Результат: 16

Преимущества лямбда-выражений

Лямбда-выражения упрощают написание кода и делают его более читабельным. Например, они могут использоваться с методами высшего порядка, такими как filter , map и reduce , для обработки коллекций данных без необходимости писать многословные анонимные классы.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); List<Integer> evenNumbers = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList());

�� С использованием лямбда-выражений код становится более кратким и выразительным, позволяя разработчикам сосредоточиться на решении задачи, а не на синтаксисе.

Заключение

Лямбда-выражения — это мощная и гибкая функция Java, которая упрощает написание кода и делает его более читабельным. Они играют ключевую роль в функциональном программировании на Java и являются неотъемлемой частью современного Java-разработчика.

Популярно о лямбда-выражениях в Java. С примерами и задачами. Часть 2

Java-университет

Нет. Переменная counter должна быть final . Или не обязательно final , но в любом случае изменять свое значение она не может. Тот же принцип используется и в лямбда-выражениях. Они имеют доступ ко всем переменным, которые им «видны» из того места, где они объявлены. Но лямбда не должна их изменять (присваивать новое значение). Правда, есть вариант обхода этого ограничения в анонимных классах. Достаточно лишь создать переменную ссылочного типа и менять внутреннее состояние объекта. При этом сама переменная будет указывать на тот же объект, и в таком случае можно смело указывать её как final .

 final AtomicInteger counter = new AtomicInteger(0); Runnable r = new Runnable() < @Override public void run() < counter.incrementAndGet(); >>; 

Здесь у нас переменная counter является ссылкой на объект типа AtomicInteger . А для изменения состояния этого объекта используется метод incrementAndGet() . Значение самой переменной во время работы программы не меняется и всегда указывает на один и тот же объект, что позволяет нам объявить переменную сразу с ключевым словом final . Эти же примеры, но с лямбда-выражениями:

 int counter = 0; Runnable r = () -> counter++; 

Не скомпилируется по той же причине, что и вариант с анонимным классом: counter не должна меняться во время работы программы. Зато вот так — все нормально:

 final AtomicInteger counter = new AtomicInteger(0); Runnable r = () -> counter.incrementAndGet(); 

Это касается и вызова методов. Изнутри лямбда-выражения можно не только обращаться ко всем «видимым» переменным, но и вызывать те методы, к которым есть доступ.

 public class Main < public static void main(String[] args) < Runnable runnable = () ->staticMethod(); new Thread(runnable).start(); > private static void staticMethod() < System.out.println("Я - метод staticMethod(), и меня только-что кто-то вызвал!"); >> 

Хотя метод staticMethod() и приватный, но он «доступен» для вызова внутри метода main() , поэтому точно так же доступен для вызова изнутри лямбды, которая создана в методе main .

Момент выполнения кода лямбда-выражения

Возможно, вам этот вопрос покажется слишком простым, но его всё следует задать: когда выполнится код внутри лямбда-выражения? В момент создания? Или же в тот момент, когда (еще и неизвестно где) оно будет вызвано? Проверить довольно просто.

 System.out.println("Запуск программы"); // много всякого разного кода // . System.out.println("Перед объявлением лямбды"); Runnable runnable = () -> System.out.println("Я - лямбда!"); System.out.println("После объявления лямбды"); // много всякого другого кода // . System.out.println("Перед передачей лямбды в тред"); new Thread(runnable).start(); 

Вывод на экран:

 Запуск программы Перед объявлением лямбды После объявления лямбды Перед передачей лямбды в тред Я - лямбда! 

Видно, что код лямбда-выражения выполнился в самом конце, после того, как был создан тред и лишь когда процесс выполнения программы дошел до фактического выполнения метода run() . А вовсе не в момент его объявления. Объявив лямбда-выражение, мы лишь создали объект типа Runnable и описали поведение его метода run() . Сам же метод был запущен значительно позже.

Method References (Ссылки на методы)?

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

 x -> System.out.println(x) 

Ему передали некий х , а оно — просто вызвало System.out.println() и передало туда х . В таком случае, мы можем заменить его на ссылку на нужный нам метод. Вот так:

 System.out::println 

Да, без скобок в конце! Более полный пример:

 List strings = new LinkedList<>(); strings.add("мама"); strings.add("мыла"); strings.add("раму"); strings.forEach(x -> System.out.println(x)); 

В последней строке мы используем метод forEach() , который принимает объект интерфейса Consumer . Это снова же функциональный интерфейс, у которого только один метод void accept(T t) . Соответственно, мы пишем лямбда-выражение, которое принимает один параметр (поскольку он типизирован в самом интерфейсе, тип параметра мы не указываем, а указываем, что называться он у нас будет х) . В теле лямбда-выражения пишем код, который будет выполняться при вызове метода accept() . Здесь мы просто выводим на экран то, что попало в переменную х . Сам же метод forEach() проходит по всем элементам коллекции, вызывает у переданного ему объекта интерфейса Consumer (нашей лямбды) метод accept() , куда и передает каждый элемент из коллекции. Как я уже сказал, такое лямбда-выражение (просто вызывающее другой метод) мы можем заменить ссылкой на нужный нам метод. Тогда наш код будет выглядеть так:

 List strings = new LinkedList<>(); strings.add("мама"); strings.add("мыла"); strings.add("раму"); strings.forEach(System.out::println); 

Главное, чтобы совпадали принимаемые параметры методов (println() и accept()) . Поскольку метод println() может принимать что угодно (он перегружен для всех примитивов и для любых объектов, мы можем вместо лямбда-выражения передать в forEach() просто ссылку на метод println() . Тогда forEach() будет брать каждый элемент коллекции и передавать его напрямую в метод println() . Кто сталкивается с этим впервые, обратите внимание, что мы не вызываем метод System.out.println() (с точками между словами и со скобочками в конце), а именно передаем саму ссылку на этот метод. При такой записи

 strings.forEach(System.out.println()); 

у нас будет ошибка компиляции. Поскольку перед вызовом forEach() Java увидит, что вызывается System.out.println() , поймет, что возвращается void и будет пытаться этот void передать в forEach() , который там ждет объект типа Consumer .

Синтаксис использования Method References

  1. Передаем ссылку на статический метод ИмяКласса:: имяСтатическогоМетода?
 public class Main < public static void main(String[] args) < Liststrings = new LinkedList<>(); strings.add("мама"); strings.add("мыла"); strings.add("раму"); strings.forEach(Main::staticMethod); > private static void staticMethod(String s) < // do something >> 
 public class Main < public static void main(String[] args) < Liststrings = new LinkedList<>(); strings.add("мама"); strings.add("мыла"); strings.add("раму"); Main instance = new Main(); strings.forEach(instance::nonStaticMethod); > private void nonStaticMethod(String s) < // do something >> 
 public class Main < public static void main(String[] args) < Listusers = new LinkedList<>(); users.add(new User("Вася")); users.add(new User("Коля")); users.add(new User("Петя")); users.forEach(User::print); > private static class User < private String name; private User(String name) < this.name = name; >private void print() < System.out.println(name); >> > 

Интересное различие между анонимным классом и лямбда-выражением

В анонимном классе ключевое слово this указывает на объект этого анонимного класса. А если использовать this внутри лямбды, мы получим доступ к объекту обрамляющего класса. Того, где мы это выражение, собственно, и написали. Так происходит потому, что лямбда-выражения при компиляции превращаются в приватный метод того класса, где они написаны. Использовать эту «фичу» я бы не рекомендовал, поскольку у неё есть побочный эффект (side effect), что противоречит принципам функционального программирования. Зато такой подход вполне соответствует ООП. 😉

Откуда я брал информацию или что почитать еще

  • Туториал на официальном сайте Oracle. Много, детально, с примерами, но на английском.
  • Тот же "ораклавский" туториал, но глава именно про Method References.
  • Статья на хабре про функциональное программирование (перевод другой статьи). Многобуков и про лямбды очень мало, так как там о функциональном программировании в общем.
  • Любителям позалипать на википедии.
  • Ну и, конечно же, кучу всего находил в гугле 🙂

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *