Как перестать беспокоится и начать работать с лямбда-выражениями в Java

Это – перевод статьи How to start working with lambda expressions, автора Luis Santiago.

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

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

Краткое введение

Из поддержки pipeline-операций в Stream API следует, что лямбда-выражения используют преимущества параллельной обработки в многоядерных системах.

Для реализации методов, определённых в функциональных интерфейсах, используются анонимные методы. Прежде чем приступить к практическим примерам важно понять, что такое функциональный интерфейс.

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

Функциональный интерфейс – это интерфейс, который содержит один, и только один (единственный!) абстрактный метод.

Если вы взглянете на сигнатуру стандартного интерфейса Runnable в Java, то наверняка заметите, что он является функциональным, так как определяет единственный метод run().

В примере ниже метод computeName абстрактный (неявно) и единственный, что делает интерфейс MyName функциональным.

interface MyName{
  String computeName(String str);
}

Оператор ->

Вместе с лямбда-выражениями в Java был добавлен новый оператор ->, который делит выражение на две части: (n) -> n * n.

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

Правая часть – тело выражения, которое задаёт действия. Для большей наглядности можно воспринимать этот оператор как глагол “становится”. Например “n становится n * n” .

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

interface NumericTest {
	boolean computeTest(int n); 
}
public static void main(String args[]) {
	NumericTest isEven = (n) -> (n % 2) == 0;
	NumericTest isNegative = (n) -> (n < 0);

	// Output: false
	System.out.println(isEven.computeTest(5));

	// Output: true
	System.out.println(isNegative.computeTest(-5));
}

И ещё один пример:

interface MyGreeting {
	String processName(String str);
}

public static void main(String args[]) {
	MyGreeting morningGreeting = (str) -> "Good Morning " + str + "!";
	MyGreeting eveningGreeting = (str) -> "Good Evening " + str + "!";
  
  	// Output: Good Morning Luis! 
	System.out.println(morningGreeting.processName("Luis"));
	
	// Output: Good Evening Jessica!
	System.out.println(eveningGreeting.processName("Jessica"));	
}

Переменные morningGreeting и eveningGreeting ссылаются на интерфейс MyGreeting и определяют разные выражения для приветствий.

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

MyGreeting morningGreeting = (String str) -> "Good Morning " + str + "!";
MyGreeting eveningGreeting = (String str) -> "Good Evening " + str + "!";

Блочные лямбда-выражения

Пока мы рассматривали только примеры одиночных выражений. Существует ещё один тип, в котором код правой части выражения состоит из нескольких операторов:

interface MyString {
    String myStringFunction(String str);
}

class Test {
    public static void main(String[] args) {
        // Block lambda to reverse string
        MyString reverseStr = (str) -> {
            String result = "";

            for(int i = str.length()-1; i >= 0; i--)
                result += str.charAt(i);

            return result;
        };

        // Output: omeD adbmaL
        System.out.println(reverseStr.myStringFunction("Lambda Demo"));
    }
}

Обобщённые функциональные интерфейсы

Лямбда-выражение не может быть обобщённым, но связанный с ним функциональный интерфейс – вполне. Можно создать один обобщённый интерфейс и работать с разными возвращаемыми типами:

interface MyGeneric<T> {
	T compute(T t);
}

public static void main(String args[]){

	// String version of MyGenericInteface
	MyGeneric<String> reverse = (str) -> {
		String result = "";
		
		for(int i = str.length()-1; i >= 0; i--)
			result += str.charAt(i);
		
		return result;
	};

	// Integer version of MyGeneric
	MyGeneric<Integer> factorial = (Integer n) -> {
		int result = 1;
		
		for(int i=1; i <= n; i++)
			result = i * result;
		
		return result;
	};

	// Output: omeD adbmaL
	System.out.println(reverse.compute("Lambda Demo")); 

	// Output: 120
	System.out.println(factorial.compute(5)); 

}

Лямбда-выражения в качестве аргументов

Одно из обычных применений лямбда-выражений – это их использование в качестве аргументов.

Они могут использоваться везде, где возвращается конечный тип. Это позволяет передавать функции в качестве аргументов.

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

interface MyString {
	String myStringFunction(String str);
}

public static String reverseStr(MyString reverse, String str){
  return reverse.myStringFunction(str);
}

public static void main (String args[]) {
	// Block lambda to reverse string
	MyString reverse = (str) -> {
		String result = "";
		
		for(int i = str.length() - 1; i >= 0; i--)
			result += str.charAt(i);
		
		return result;
	};

	// Output: omeD adbmaL
	System.out.println(reverseStr(reverse, "Lambda Demo")); 
}

Эти концепции дадут вам неплохую базу для начала работы с лямбда-выражениями.