Лямбда-выражения Java
Лямбда-выражения Java
В этой статье мы узнаем о лямбда-выражении Java и использовании лямбда-выражения с функциональными интерфейсами, общим функциональным интерфейсом и потоковым API с помощью примеров.
Лямбда-выражение было впервые представлено в Java 8. Его основная цель — увеличить выразительную мощь языка.
Но прежде чем приступить к лямбда-выражениям, нам сначала нужно понять функциональные интерфейсы.
<час>Что такое функциональный интерфейс?
Если интерфейс Java содержит один и только один абстрактный метод, то он называется функциональным интерфейсом. Это единственный метод, определяющий назначение интерфейса.
Например, Runnable
интерфейс из пакета java.lang
; является функциональным интерфейсом, поскольку он представляет собой только один метод, т.е. run()
.
Пример 1. Определение функционального интерфейса в Java
import java.lang.FunctionalInterface;
@FunctionalInterface
public interface MyInterface{
// the single abstract method
double getValue();
}
В приведенном выше примере интерфейс MyInterface имеет только один абстрактный метод getValue(). Следовательно, это функциональный интерфейс.
Здесь мы использовали аннотацию @FunctionalInterface
. Аннотация заставляет компилятор Java указать, что интерфейс является функциональным интерфейсом. Следовательно, не позволяет иметь более одного абстрактного метода. Однако это не является обязательным.
В Java 7 функциональные интерфейсы рассматривались как отдельные абстрактные методы или SAM. тип. SAM обычно реализовывались с помощью анонимных классов в Java 7.
Пример 2. Реализация SAM с анонимными классами в Java
public class FunctionInterfaceTest {
public static void main(String[] args) {
// anonymous class
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("I just implemented the Runnable Functional Interface.");
}
}).start();
}
}
Вывод :
I just implemented the Runnable Functional Interface.
Здесь мы можем передать анонимный класс методу. Это помогает писать программы с меньшим количеством кода на Java 7. Однако синтаксис по-прежнему был сложным, и требовалось много дополнительных строк кода.
Java 8 расширила возможности SAM, сделав еще один шаг вперед. Поскольку мы знаем, что функциональный интерфейс имеет только один метод, нет необходимости определять имя этого метода при его передаче в качестве аргумента. Лямбда-выражение позволяет нам сделать именно это.
<час>Введение в лямбда-выражения
Лямбда-выражение — это, по сути, анонимный или безымянный метод. Лямбда-выражение не выполняется само по себе. Вместо этого он используется для реализации метода, определенного функциональным интерфейсом.
Как определить лямбда-выражение в Java?
Вот как мы можем определить лямбда-выражение в Java.
(parameter list) -> lambda body
Новый оператор (->
) известен как оператор стрелки или лямбда-оператор. Синтаксис может быть не ясен в данный момент. Давайте рассмотрим несколько примеров,
Предположим, у нас есть такой метод:
double getPiValue() {
return 3.1415;
}
Мы можем написать этот метод, используя лямбда-выражение, как:
() -> 3.1415
Здесь метод не имеет параметров. Следовательно, левая часть оператора содержит пустой параметр. Правая часть — это тело лямбда-выражения, определяющее действие лямбда-выражения. В этом случае возвращается значение 3,1415.
<час>Типы лямбда-тела
В Java тело лямбды бывает двух типов.
<сильный>1. Тело с одним выражением
() -> System.out.println("Lambdas are great");
Этот тип лямбда-тела называется телом выражения.
<сильный>2. Тело, состоящее из блока кода.
() -> {
double pi = 3.1415;
return pi;
};
Этот тип лямбда-тела известен как блочное тело. Тело блока позволяет телу лямбды включать несколько операторов. Эти операторы заключены в фигурные скобки, после которых необходимо добавить точку с запятой.
Примечание :Для тела блока у вас может быть оператор возврата, если тело возвращает значение. Однако тело выражения не требует оператора return.
<час>Пример 3. Лямбда-выражение
Напишем программу на Java, которая возвращает значение числа Пи с помощью лямбда-выражения.
Как упоминалось ранее, лямбда-выражение не выполняется само по себе. Скорее, он формирует реализацию абстрактного метода, определенного функциональным интерфейсом.
Итак, сначала нам нужно определить функциональный интерфейс.
import java.lang.FunctionalInterface;
// this is functional interface
@FunctionalInterface
interface MyInterface{
// abstract method
double getPiValue();
}
public class Main {
public static void main( String[] args ) {
// declare a reference to MyInterface
MyInterface ref;
// lambda expression
ref = () -> 3.1415;
System.out.println("Value of Pi = " + ref.getPiValue());
}
}
Вывод :
Value of Pi = 3.1415
В приведенном выше примере
- Мы создали функциональный интерфейс с именем MyInterface. . Он содержит единственный абстрактный метод с именем
getPiValue()
. - Внутри Главного class, мы объявили ссылку на MyInterface . Обратите внимание, что мы можем объявить ссылку на интерфейс, но не можем создать экземпляр интерфейса. То есть,
// it will throw an error MyInterface ref = new myInterface(); // it is valid MyInterface ref;
- Затем мы присвоили ссылке лямбда-выражение.
ref = () -> 3.1415;
- Наконец, мы вызываем метод
getPiValue()
с помощью эталонного интерфейса. Когда
System.out.println("Value of Pi = " + ref.getPiValue());
Лямбда-выражения с параметрами
До сих пор мы создавали лямбда-выражения без каких-либо параметров. Однако, подобно методам, лямбда-выражения также могут иметь параметры. Например,
(n) -> (n%2)==0
Здесь переменная n внутри круглых скобок — это параметр, передаваемый в лямбда-выражение. Тело лямбды принимает параметр и проверяет, является ли он четным или нечетным.
Пример 4. Использование лямбда-выражения с параметрами
@FunctionalInterface
interface MyInterface {
// abstract method
String reverse(String n);
}
public class Main {
public static void main( String[] args ) {
// declare a reference to MyInterface
// assign a lambda expression to the reference
MyInterface ref = (str) -> {
String result = "";
for (int i = str.length()-1; i >= 0 ; i--)
result += str.charAt(i);
return result;
};
// call the method of the interface
System.out.println("Lambda reversed = " + ref.reverse("Lambda"));
}
}
Вывод :
Lambda reversed = adbmaL<час>
Универсальный функциональный интерфейс
До сих пор мы использовали функциональный интерфейс, который принимает только один тип значения. Например,
@FunctionalInterface
interface MyInterface {
String reverseString(String n);
}
Приведенный выше функциональный интерфейс принимает только String
и возвращает String
. Однако мы можем сделать функциональный интерфейс универсальным, чтобы можно было использовать любой тип данных. Если вы не уверены в универсальных шаблонах, посетите Java Generics.
Пример 5. Общий функциональный интерфейс и лямбда-выражения
// GenericInterface.java
@FunctionalInterface
interface GenericInterface<T> {
// generic method
T func(T t);
}
// GenericLambda.java
public class Main {
public static void main( String[] args ) {
// declare a reference to GenericInterface
// the GenericInterface operates on String data
// assign a lambda expression to it
GenericInterface<String> reverse = (str) -> {
String result = "";
for (int i = str.length()-1; i >= 0 ; i--)
result += str.charAt(i);
return result;
};
System.out.println("Lambda reversed = " + reverse.func("Lambda"));
// declare another reference to GenericInterface
// the GenericInterface operates on Integer data
// assign a lambda expression to it
GenericInterface<Integer> factorial = (n) -> {
int result = 1;
for (int i = 1; i <= n; i++)
result = i * result;
return result;
};
System.out.println("factorial of 5 = " + factorial.func(5));
}
}
Вывод :
Lambda reversed = adbmaL factorial of 5 = 120
В приведенном выше примере мы создали общий функциональный интерфейс с именем GenericInterface. . Он содержит универсальный метод с именем func()
. .
Здесь, внутри класса Main,
GenericInterface<String> reverse
- создает ссылку на интерфейс. Интерфейс теперь работает наString
тип данных.GenericInterface<Integer> factorial
- создает ссылку на интерфейс. Интерфейс в этом случае работает наInteger
тип данных.
Лямбда-выражение и Stream API
В JDK8 добавлен новый пакет java.util.stream, который позволяет разработчикам Java выполнять такие операции, как поиск, фильтрация, сопоставление, сокращение или управление коллекциями, такими как Lists
. .
Например, у нас есть поток данных (в нашем случае List
из String
), где каждая строка представляет собой комбинацию названия страны и места страны. Теперь мы можем обработать этот поток данных и получить только места из Непала.
Для этого мы можем выполнять массовые операции в потоке с помощью комбинации Stream API и лямбда-выражения.
Пример 6. Демонстрация использования лямбда-выражений с Stream API
import java.util.ArrayList;
import java.util.List;
public class StreamMain {
// create an object of list using ArrayList
static List<String> places = new ArrayList<>();
// preparing our data
public static List getPlaces(){
// add places and country to the list
places.add("Nepal, Kathmandu");
places.add("Nepal, Pokhara");
places.add("India, Delhi");
places.add("USA, New York");
places.add("Africa, Nigeria");
return places;
}
public static void main( String[] args ) {
List<String> myPlaces = getPlaces();
System.out.println("Places from Nepal:");
// Filter places from Nepal
myPlaces.stream()
.filter((p) -> p.startsWith("Nepal"))
.map((p) -> p.toUpperCase())
.sorted()
.forEach((p) -> System.out.println(p));
}
}
Вывод :
Places from Nepal: NEPAL, KATHMANDU NEPAL, POKHARA
В приведенном выше примере обратите внимание на оператор
myPlaces.stream()
.filter((p) -> p.startsWith("Nepal"))
.map((p) -> p.toUpperCase())
.sorted()
.forEach((p) -> System.out.println(p));
Здесь мы используем такие методы, как filter()
, map()
и forEach()
потокового API. Эти методы могут принимать лямбда-выражение в качестве входных данных.
Мы также можем определить наши собственные выражения на основе синтаксиса, который мы изучили выше. Это позволяет нам значительно сократить количество строк кода, как мы видели в приведенном выше примере.
java