English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
В этой статье мы рассмотрим примеры Java lambda-выражений, а также использование lambda-выражений с функциональными интерфейсами, гетерогенными функциональными интерфейсами и API потоков.
Лямбда-выражения были впервые введены в Java 8. Основная цель - повысить表达能力 языка.
Однако, до изучения lambda, нам сначала нужно понять функциональные интерфейсы.
Если интерфейс Java содержит только один абстрактный метод, то его называют функциональным интерфейсом. Только этот метод определяет предполагаемое использование интерфейса.
Например, интерфейс Runnable в пакете java.lang; является функциональным интерфейсом, потому что он содержит только один метод, то есть run().
import java.lang.FunctionalInterface; @FunctionalInterface public interface MyInterface{ // Один абстрактный метод double getValue(); }
В приведенном выше примере интерфейс MyInterface имеет один абстрактный метод getValue(). Поэтому он является функциональным интерфейсом.
Здесь мы используем аннотацию @FunctionalInterface. Эта аннотация заставляет компилятор Java указывать, что интерфейс является функциональным интерфейсом. Поэтому не допускается наличие нескольких абстрактных методов. Однако, это не обязательно.
In Java 7, functional interfaces are consideredSingle Abstract Method (SAM)Type. In Java 7, SAM types are usually implemented through anonymous classes.
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.
Here, we can pass an anonymous class to a method. This helps to write less code in Java 7. However, the syntax is still difficult and requires a large number of additional code lines.
Java 8 further extends the functionality of SAM. Since we know that a functional interface has only one method, there is no need to define the name of the method when passing it as a parameter. Lambda expressions allow us to do this.
A lambda expression is essentially an anonymous or unnamed method. A lambda expression cannot be executed alone. Instead, it is used to implement methods defined by functional interfaces.
This is how we define a lambda expression in Java.
(parameter list) -> lambda body
The new operator used (->) is called the arrow operator or lambda operator. Let's explore some examples,
Suppose we have such a method:
double getPiValue() { return 3.1415; }
We can write this method using a lambda expression as follows:
() -> 3.1415
Here, the method has no parameters. Therefore, the left side of the operator includes an empty parameter. The right side is the lambda body, which specifies the operation of the lambda expression. In this case, it will return the value 3.1415.
In Java, there are two types of lambda bodies.
1. One-liner expression body
() -> System.out.println("Lambdas are great");
Этот тип лямбда-тела называется выражением.
2. Тело, состоящее из блоков кода.
() -> { double pi = 3.1415; return pi; };
Этот тип лямбда-тела называется блоком. Блок лямбда-тела позволяет лямбда-телу содержать несколько предложений. Эти предложения заключены в скобки, и вы должны добавить точку с запятой после скобок.
Внимание: Для блока тела всегда должен быть один return-утверждение. Однако, для одного выражения в теле не требуется return-утверждение.
Давайте напишем Java-программу, которая использует lambda-выражение для возвращения значения Pi.
Как уже упоминалось, lambda-выражения не выполняются в отдельности. Напротив, они представляют собой реализацию абстрактных методов, определяемых функциональным интерфейсом.
Таким образом, нам нужно сначала определить функциональный интерфейс.
import java.lang.FunctionalInterface; // Это функциональный интерфейс @FunctionalInterface interface MyInterface{ // абстрактный метод double getPiValue(); } public class Main { public static void main(String[] args) { //Указание на ссылку на MyInterface MyInterface ref; // lambda-выражение ref = () -> 3.1415; System.out.println("Pi = " + ref.getPiValue()); } }
Вывод:
Pi = 3.1415
В вышеуказанных примерах:
Мы создали функциональный интерфейс под названием MyInterface. Он содержит абстрактный метод под названием getPiValue()
Внутри класса Main мы объявляем ссылку на MyInterface. Обратите внимание, что мы можем объявить ссылку на интерфейс, но не можем instantiate интерфейс. Это потому что:
// Это вызовет ошибку MyInterface ref = new myInterface(); // Это эффективно MyInterface ref;
Затем мы назначили lambda-выражение переменной ref.
ref = () -> 3.1415;
В конце мы используем метод getPiValue() интерфейса reference.
System.out.println("Pi = " + ref.getPiValue());
До сих пор мы создали lambda-выражения без параметров. Однако, как и методы, lambda-выражения также могут иметь параметры. Например:
(n) -> (n % 2) == 0
В данном случае, переменная n в скобках передается в lambda-выражение. Лямбда-тело принимает аргумент и проверяет, является ли он четным или нечетным.
@FunctionalInterface interface MyInterface { //Абстрактный метод String reverse(String n); } public class Main { public static void main(String[] args) { //Указание на ссылку на MyInterface // присвоить лямбда-выражение ссылке MyInterface ref = (str) -> { String result = ""; for (int i = str.length() - 1; i >= 0; i--){ result += str.charAt(i); } return result; }; // вызов метода интерфейса System.out.println("Lambda reversed = " + ref.reverse("Lambda")); } }
Вывод:
Lambda reversed = adbmaL
До сих пор мы использовали функциональные интерфейсы, принимающие только один тип данных. Например:
@FunctionalInterface interface MyInterface { String reverseString(String n); }
Этот функциональный интерфейс принимает только String и возвращает String. Но мы можем сделать функциональный интерфейс универсальным, чтобы он принимал любую тип данных. Если вы не знакомы с генериками, пожалуйста, посетитеJava генерические типы.
// GenericInterface.java @FunctionalInterface interface GenericInterface<T> { // генерическая функция T func(T t); } // GenericLambda.java public class Main { public static void main(String[] args) { // объявить ссылку на GenericInterface // GenericInterface выполняет операции со строками // для него分配ить лямбда-выражение 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")); // объявить другую ссылку на GenericInterface // GenericInterface выполняет операции с целыми числами // для него分配ить лямбда-выражение GenericInterface<Integer> factorial = (n) -> { int result = 1; for (int i = 1; i <= n; i++) result = i * result; return result; }; System.out.println("5! = " + factorial.func(5)); } }
Вывод:
Lambda reversed = adbmaL 5! = 120
В предыдущем примере мы создали интерфейс GenericInterface с генерическим методом func().
Внутри класса:
GenericInterface<String> reverse - создание ссылки на этот интерфейс. Теперь интерфейс может обрабатывать данные типа String.
GenericInterface<Integer> factorial - создание ссылки на этот интерфейс. В этом случае интерфейс работает с типом данных Integer.
Новыйjava.util.streamПакет добавлен в JDK8, он позволяет java-разработчикам выполнять операции поиска, фильтрации, маппинга, уменьшения и т.д. или операции на списках и других коллекциях.
Например, у нас есть поток данных (в нашем примере это список строк), где каждая строка представляет собой комбинацию имени страны и региона/территории. Теперь мы можем обработать этот поток данных и извлечь位置的 данные только из Непала.
Для этого мы можем использовать Stream API и Lambda-выражения для выполнения параллельных операций на потоке.
import java.util.ArrayList; import java.util.List; public class StreamMain { //使用ArrayList创建一个列表对象 static List<String> places = new ArrayList<>(); //准备我们的数据 public static List getPlaces(){ //将地点和国家添加到列表中 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("Места из Непала:"); myPlaces.stream() .filter((p) -> p.startsWith("Nepal")) .map((p) -> p.toUpperCase()) .sorted() .forEach((p) -> System.out.println(p)); } }
Вывод:
Места из Непала: NEPAL, KATHMANDU NEPAL, POKHARA
В примере выше обратите внимание на следующие строки:
myPlaces.stream() .filter((p) -> p.startsWith("Nepal")) .map((p) -> p.toUpperCase()) .sorted() .forEach((p) -> System.out.println(p));
Здесь мы используем методы Stream API filter(), map() и forEach() и т.д. Эти методы могут принимать lambda-выражение в качестве входных данных.
Мы можем определить свои собственные выражения, основываясь на изученном синтаксисе. Как показано в примере выше, это позволяет нам значительно уменьшить количество строк кода.