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

от admin

Lambda Expressions

One issue with anonymous classes is that if the implementation of your anonymous class is very simple, such as an interface that contains only one method, then the syntax of anonymous classes may seem unwieldy and unclear. In these cases, you’re usually trying to pass functionality as an argument to another method, such as what action should be taken when someone clicks a button. Lambda expressions enable you to do this, to treat functionality as method argument, or code as data.

The previous section, Anonymous Classes, shows you how to implement a base class without giving it a name. Although this is often more concise than a named class, for classes with only one method, even an anonymous class seems a bit excessive and cumbersome. Lambda expressions let you express instances of single-method classes more compactly.

This section covers the following topics:

Ideal Use Case for Lambda Expressions

Suppose that you are creating a social networking application. You want to create a feature that enables an administrator to perform any kind of action, such as sending a message, on members of the social networking application that satisfy certain criteria. The following table describes this use case in detail:

  1. Administrator specifies criteria of members on which to perform a certain action.
  2. Administrator specifies an action to perform on those selected members.
  3. Administrator selects the Submit button.
  4. The system finds all members that match the specified criteria.
  5. The system performs the specified action on all matching members.

1a. Administrator has an option to preview those members who match the specified criteria before he or she specifies the action to be performed or before selecting the Submit button.

Suppose that members of this social networking application are represented by the following Person class:

Suppose that the members of your social networking application are stored in a List<Person> instance.

This section begins with a naive approach to this use case. It improves upon this approach with local and anonymous classes, and then finishes with an efficient and concise approach using lambda expressions. Find the code excerpts described in this section in the example RosterTest .

Approach 1: Create Methods That Search for Members That Match One Characteristic

One simplistic approach is to create several methods; each method searches for members that match one characteristic, such as gender or age. The following method prints members that are older than a specified age:

Note: A List is an ordered Collection . A collection is an object that groups multiple elements into a single unit. Collections are used to store, retrieve, manipulate, and communicate aggregate data. For more information about collections, see the Collections trail.

This approach can potentially make your application brittle, which is the likelihood of an application not working because of the introduction of updates (such as newer data types). Suppose that you upgrade your application and change the structure of the Person class such that it contains different member variables; perhaps the class records and measures ages with a different data type or algorithm. You would have to rewrite a lot of your API to accommodate this change. In addition, this approach is unnecessarily restrictive; what if you wanted to print members younger than a certain age, for example?

Approach 2: Create More Generalized Search Methods

The following method is more generic than printPersonsOlderThan ; it prints members within a specified range of ages:

What if you want to print members of a specified sex, or a combination of a specified gender and age range? What if you decide to change the Person class and add other attributes such as relationship status or geographical location? Although this method is more generic than printPersonsOlderThan , trying to create a separate method for each possible search query can still lead to brittle code. You can instead separate the code that specifies the criteria for which you want to search in a different class.

Approach 3: Specify Search Criteria Code in a Local Class

The following method prints members that match search criteria that you specify:

This method checks each Person instance contained in the List parameter roster whether it satisfies the search criteria specified in the CheckPerson parameter tester by invoking the method tester.test . If the method tester.test returns a true value, then the method printPersons is invoked on the Person instance.

To specify the search criteria, you implement the CheckPerson interface:

The following class implements the CheckPerson interface by specifying an implementation for the method test . This method filters members that are eligible for Selective Service in the United States: it returns a true value if its Person parameter is male and between the ages of 18 and 25:

To use this class, you create a new instance of it and invoke the printPersons method:

Although this approach is less brittle—you don't have to rewrite methods if you change the structure of the Person —you still have additional code: a new interface and a local class for each search you plan to perform in your application. Because CheckPersonEligibleForSelectiveService implements an interface, you can use an anonymous class instead of a local class and bypass the need to declare a new class for each search.

Approach 4: Specify Search Criteria Code in an Anonymous Class

One of the arguments of the following invocation of the method printPersons is an anonymous class that filters members that are eligible for Selective Service in the United States: those who are male and between the ages of 18 and 25:

This approach reduces the amount of code required because you don't have to create a new class for each search that you want to perform. However, the syntax of anonymous classes is bulky considering that the CheckPerson interface contains only one method. In this case, you can use a lambda expression instead of an anonymous class, as described in the next section.

Approach 5: Specify Search Criteria Code with a Lambda Expression

The CheckPerson interface is a functional interface. A functional interface is any interface that contains only one abstract method. (A functional interface may contain one or more default methods or static methods.) Because a functional interface contains only one abstract method, you can omit the name of that method when you implement it. To do this, instead of using an anonymous class expression, you use a lambda expression, which is highlighted in the following method invocation:

See Syntax of Lambda Expressions for information about how to define lambda expressions.

You can use a standard functional interface in place of the interface CheckPerson , which reduces even further the amount of code required.

Approach 6: Use Standard Functional Interfaces with Lambda Expressions

Reconsider the CheckPerson interface:

This is a very simple interface. It’s a functional interface because it contains only one abstract method. This method takes one parameter and returns a boolean value. The method is so simple that it might not be worth it to define one in your application. Consequently, the JDK defines several standard functional interfaces, which you can find in the package java.util.function .

For example, you can use the Predicate<T> interface in place of CheckPerson . This interface contains the method boolean test(T t) :

The interface Predicate<T> is an example of a generic interface. (For more information about generics, see the Generics (Updated) lesson.) Generic types (such as generic interfaces) specify one or more type parameters within angle brackets ( <> ). This interface contains only one type parameter, T . When you declare or instantiate a generic type with actual type arguments, you have a parameterized type. For example, the parameterized type Predicate<Person> is the following:

This parameterized type contains a method that has the same return type and parameters as CheckPerson.boolean test(Person p) . Consequently, you can use Predicate<T> in place of CheckPerson as the following method demonstrates:

As a result, the following method invocation is the same as when you invoked printPersons in Approach 3: Specify Search Criteria Code in a Local Class to obtain members who are eligible for Selective Service:

This is not the only possible place in this method to use a lambda expression. The following approach suggests other ways to use lambda expressions.

Approach 7: Use Lambda Expressions Throughout Your Application

Reconsider the method printPersonsWithPredicate to see where else you could use lambda expressions:

This method checks each Person instance contained in the List parameter roster whether it satisfies the criteria specified in the Predicate parameter tester . If the Person instance does satisfy the criteria specified by tester , the method printPerson is invoked on the Person instance.

Instead of invoking the method printPerson , you can specify a different action to perform on those Person instances that satisfy the criteria specified by tester . You can specify this action with a lambda expression. Suppose you want a lambda expression similar to printPerson , one that takes one argument (an object of type Person ) and returns void. Remember, to use a lambda expression, you need to implement a functional interface. In this case, you need a functional interface that contains an abstract method that can take one argument of type Person and returns void. The Consumer<T> interface contains the method void accept(T t) , which has these characteristics. The following method replaces the invocation p.printPerson() with an instance of Consumer<Person> that invokes the method accept :

As a result, the following method invocation is the same as when you invoked printPersons in Approach 3: Specify Search Criteria Code in a Local Class to obtain members who are eligible for Selective Service. The lambda expression used to print members is highlighted:

What if you want to do more with your members’ profiles than printing them out. Suppose that you want to validate the members’ profiles or retrieve their contact information? In this case, you need a functional interface that contains an abstract method that returns a value. The Function<T,R> interface contains the method R apply(T t) . The following method retrieves the data specified by the parameter mapper , and then performs an action on it specified by the parameter block :

The following method retrieves the email address from each member contained in roster who is eligible for Selective Service and then prints it:

Approach 8: Use Generics More Extensively

Reconsider the method processPersonsWithFunction . The following is a generic version of it that accepts, as a parameter, a collection that contains elements of any data type:

To print the e-mail address of members who are eligible for Selective Service, invoke the processElements method as follows:

This method invocation performs the following actions:

  1. Obtains a source of objects from the collection source . In this example, it obtains a source of Person objects from the collection roster . Notice that the collection roster , which is a collection of type List , is also an object of type Iterable .
  2. Filters objects that match the Predicate object tester . In this example, the Predicate object is a lambda expression that specifies which members would be eligible for Selective Service.
  3. Maps each filtered object to a value as specified by the Function object mapper . In this example, the Function object is a lambda expression that returns the e-mail address of a member.
  4. Performs an action on each mapped object as specified by the Consumer object block . In this example, the Consumer object is a lambda expression that prints a string, which is the e-mail address returned by the Function object.

You can replace each of these actions with an aggregate operation.

Approach 9: Use Aggregate Operations That Accept Lambda Expressions as Parameters

The following example uses aggregate operations to print the e-mail addresses of those members contained in the collection roster who are eligible for Selective Service:

The following table maps each of the operations the method processElements performs with the corresponding aggregate operation:

processElements Action Aggregate Operation
Obtain a source of objects Stream<E> stream()
Filter objects that match a Predicate object Stream<T> filter(Predicate<? super T> predicate)
Map objects to another value as specified by a Function object <R> Stream<R> map(Function<? super T,? extends R> mapper)
Perform an action as specified by a Consumer object void forEach(Consumer<? super T> action)

The operations filter , map , and forEach are aggregate operations. Aggregate operations process elements from a stream, not directly from a collection (which is the reason why the first method invoked in this example is stream ). A stream is a sequence of elements. Unlike a collection, it is not a data structure that stores elements. Instead, a stream carries values from a source, such as collection, through a pipeline. A pipeline is a sequence of stream operations, which in this example is filter — map — forEach . In addition, aggregate operations typically accept lambda expressions as parameters, enabling you to customize how they behave.

For a more thorough discussion of aggregate operations, see the Aggregate Operations lesson.

Lambda Expressions in GUI Applications

To process events in a graphical user interface (GUI) application, such as keyboard actions, mouse actions, and scroll actions, you typically create event handlers, which usually involves implementing a particular interface. Often, event handler interfaces are functional interfaces; they tend to have only one method.

In the JavaFX example HelloWorld.java (discussed in the previous section Anonymous Classes), you can replace the highlighted anonymous class with a lambda expression in this statement:

The method invocation btn.setOnAction specifies what happens when you select the button represented by the btn object. This method requires an object of type EventHandler<ActionEvent> . The EventHandler<ActionEvent> interface contains only one method, void handle(T event) . This interface is a functional interface, so you could use the following highlighted lambda expression to replace it:

Syntax of Lambda Expressions

A lambda expression consists of the following:

A comma-separated list of formal parameters enclosed in parentheses. The CheckPerson.test method contains one parameter, p , which represents an instance of the Person class.

Читать:
Почему вместо ummy video downloader устанавливается televzr

Note: You can omit the data type of the parameters in a lambda expression. In addition, you can omit the parentheses if there is only one parameter. For example, the following lambda expression is also valid:

The arrow token, ->

A body, which consists of a single expression or a statement block. This example uses the following expression:

If you specify a single expression, then the Java runtime evaluates the expression and then returns its value. Alternatively, you can use a return statement:

A return statement is not an expression; in a lambda expression, you must enclose statements in braces ( <> ). However, you do not have to enclose a void method invocation in braces. For example, the following is a valid lambda expression:

Note that a lambda expression looks a lot like a method declaration; you can consider lambda expressions as anonymous methods—methods without a name.

The following example, Calculator , is an example of lambda expressions that take more than one formal parameter:

The method operateBinary performs a mathematical operation on two integer operands. The operation itself is specified by an instance of IntegerMath . The example defines two operations with lambda expressions, addition and subtraction . The example prints the following:

Accessing Local Variables of the Enclosing Scope

Like local and anonymous classes, lambda expressions can capture variables; they have the same access to local variables of the enclosing scope. However, unlike local and anonymous classes, lambda expressions do not have any shadowing issues (see Shadowing for more information). Lambda expressions are lexically scoped. This means that they do not inherit any names from a supertype or introduce a new level of scoping. Declarations in a lambda expression are interpreted just as they are in the enclosing environment. The following example, LambdaScopeTest , demonstrates this:

This example generates the following output:

If you substitute the parameter x in place of y in the declaration of the lambda expression myConsumer , then the compiler generates an error:

The compiler generates the error «Lambda expression’s parameter x cannot redeclare another local variable defined in an enclosing scope» because the lambda expression does not introduce a new level of scoping. Consequently, you can directly access fields, methods, and local variables of the enclosing scope. For example, the lambda expression directly accesses the parameter x of the method methodInFirstLevel . To access variables in the enclosing class, use the keyword this . In this example, this.x refers to the member variable FirstLevel.x .

However, like local and anonymous classes, a lambda expression can only access local variables and parameters of the enclosing block that are final or effectively final. In this example, the variable z is effectively final; its value is never changed after it’s initialized. However, suppose that you add the following assignment statement in the the lambda expression myConsumer :

Because of this assignment statement, the variable z is not effectively final anymore. As a result, the Java compiler generates an error message similar to «Local variable z defined in an enclosing scope must be final or effectively final».

Target Typing

How do you determine the type of a lambda expression? Recall the lambda expression that selected members who are male and between the ages 18 and 25 years:

This lambda expression was used in the following two methods:

public static void printPersons(List<Person> roster, CheckPerson tester) in Approach 3: Specify Search Criteria Code in a Local Class

When the Java runtime invokes the method printPersons , it’s expecting a data type of CheckPerson , so the lambda expression is of this type. However, when the Java runtime invokes the method printPersonsWithPredicate , it’s expecting a data type of Predicate<Person> , so the lambda expression is of this type. The data type that these methods expect is called the target type. To determine the type of a lambda expression, the Java compiler uses the target type of the context or situation in which the lambda expression was found. It follows that you can only use lambda expressions in situations in which the Java compiler can determine a target type:

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

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

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

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

В роли функционального интерфейса выступает интерфейс Operationable , в котором определен один метод без реализации — метод calculate . Данный метод принимает два параметра — целых числа, и возвращает некоторое целое число.

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

Чтобы объявить и использовать лямбда-выражение, основная программа разбивается на ряд этапов:

Определение ссылки на функциональный интерфейс:

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

Так, в методе интерфейса оба параметра представляют тип int , значит, в теле лямбда-выражения мы можем применить к ним сложение. Результат сложения также представляет тип int , объект которого возвращается методом интерфейса.

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

Так как в лямбда-выражении определена операция сложения параметров, результатом метода будет сумма чисел 10 и 20.

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

Отложенное выполнение

Одним из ключевых моментов в использовании лямбд является отложенное выполнение (deferred execution). То есть мы определяем в одном месте программы лямбда-выражение и затем можем его вызывать при необходимости неопределенное количество раз в различных частях программы. Отложенное выполнение может потребоваться, к примеру, в следующих случаях:

Выполнение кода отдельном потоке

Выполнение одного и того же кода несколько раз

Выполнение кода в результате какого-то события

Выполнение кода только в том случае, когда он действительно необходим и если он необходим

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

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

Если метод не принимает никаких параметров, то пишутся пустые скобки, например:

Если метод принимает только один параметр, то скобки можно опустить:

Терминальные лямбда-выражения

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

Лямбды и локальные переменные

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

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

Теперь рассмотрим другой пример — локальные переменные на уровне метода:

Локальные переменные уровня метода мы также можем использовать в лямбдах, но изменять их значение нельзя. Если мы попробуем это сделать, то среда разработки (Netbeans) может нам высветить ошибку и то, что такую переменную надо пометить с помощью ключевого слова final , то есть сделать константой: final int n=70; . Однако это необязательно.

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

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

Существуют два типа лямбда-выражений: однострочное выражение и блок кода. Примеры однострочных выражений демонстрировались выше. Блочные выражения обрамляются фигурными скобками. В блочных лямбда-выражениях можно использовать внутренние вложенные блоки, циклы, конструкции if, switch, создавать переменные и т.д. Если блочное лямбда-выражение должно возвращать значение, то явным образом применяется оператор return :

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

Функциональный интерфейс может быть обобщенным, однако в лямбда-выражении использование обобщений не допускается. В этом случае нам надо типизировать объект интерфейса определенным типом, который потом будет применяться в лямбда-выражении. Например:

Таким образом, при объявлении лямбда-выражения ему уже известно, какой тип параметры будут представлять и какой тип они будут возвращать.

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

λ — означает, что это лямбда-функция. Всё что после неё — список аргументов, в идеале абсолютно любого типа, в том числе может быть и другая лямбда-функция. После точки идёт «тело функции», а уже потом, собственно, идёт аргумент, который будет передан. Т.о.

Здесь в качестве параметра x выступает другая лямбда-функция λy.y + 1 , в которую передаётся параметр 2. То есть любая лямбда функция является функцией высшего порядка, может принимать в качестве аргумента другую функцию и возвращать функцию:

Если интересно, я когда-то писал аналогичные вещи на C#

Теперь давайте посмотрим, как же все наши примеры будут выглядеть на C#. Здесь в качестве лямбда-функции я использую Func, где T — тип аргумента, а U — тип возвращаемого значения:

Сложность составляет только явное указание типа аргументов и возвращаемого значения.

Lambda-выражения в Java

Привет, Хабр! Представляю вашему вниманию перевод статьи «Java Lambda Expressions» автора www.programiz.com.

Введение

В этой статье, с помощью примеров, мы изучим lambda-выражения в Java, их использование с функциональными интерфейсами, параметризированными функциональными интерфейсами и Stream API.

Лямбда выражения были добавлены в Java 8. Их основная цель – повысить читабельность и уменьшить количество кода.

Но, прежде чем перейти к лямбдам, нам необходимо понимать функциональные интерфейсы.

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

Если интерфейс в Java содержит один и только один абстрактный метод, то он называется функциональным. Этот единственный метод определяет назначение интерфейса.

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

Пример 1: объявление функционального интерфейса в java

В приведенном выше примере, интерфейс MyInterface имеет только один абстрактный метод getValue(). Значит, этот интерфейс — функциональный.

Здесь мы использовали аннотацию FunctionalInterface, которая помогает понять компилятору, что интерфейс функциональный. Следовательно, не позволяет иметь более одного абстрактного метода. Тем не менее, мы можем её опустить.

В Java 7, функциональные интерфейсы рассматривались как Single Abstract Methods (SAM). SAM обычно реализовывались с помощью анонимных классов.

Пример 2: реализация SAM с помощью анонимного класса в java

Результат выполнения:

В этом примере, мы принимаем анонимный класс для вызова метода. Это помогало писать программы с меньшим количеством строк кода в Java 7. Однако, синтаксис оставался достаточно сложным и громоздким.

Java 8 расширила возможности SAM, сделав шаг вперед. Как мы знаем, функциональный интерфейс содержит только один метод, следовательно, нам не нужно указывать название метода при передаче его в качестве аргумента. Именно это и позволяет нам lambda-выражения.

Введение в лямбда-выражения

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

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

В Java, лямбда-выражения имеют следующий синтаксис:

Здесь мы использовали новый оператор (->) — лямбда-оператор. Возможно, синтаксис кажется немного сложным. Давайте разберем пару примеров.

Предположим, у нас есть такой метод:

Мы можем записать его, используя лямбда, как:

Этот метод не имеет никаких параметров. Следовательно, левая часть выражения содержит пустые скобки. Правая сторона – тело лямбда-выражения, которое определяет его действие. В нашем случае, возвращается значение 3.1415.

Типы лямбда-выражений

В Java, тело лямбды может быть двух типов.

1. Однострочные

2. Блочные (многострочные)

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

Примечание: многострочные лямбда-выражения, всегда должны иметь оператор return, в отличии от однострочных.

Пример 3: лямбда-выражение

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

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

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

  • Мы создали функциональный интерфейс MyInterface, который содержит один абстрактный метод getPiValue().
  • Внутри класса Main, мы объявили ссылку на MyInterface. Обратите внимание, что мы можем объявить ссылку на интерфейс, но не можем создать его объект.

В этом примере, переменная n внутри скобок является параметром, переданном в лямбда-выражение. Тело лямбды принимает параметр и проверяет его на четность.

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

Результат выполнения:

Параметризированный функциональный интерфейс

До этого момента, мы использовали функциональные интерфейсы, которые принимали только один тип значения. Например:

Вышеупомянутый функциональный интерфейс принимает только String и возвращает String. Однако, мы можем сделать наш интерфейс универсальным, чтобы использовать с любым типом данных.

Пример 5: параметризированный интерфейс и лямбда-выражения

В этом примере, мы создали параметризированный функциональный интерфейс GenericInterface, который содержит параметризированный метод func().

Затем, внутри класса Main:

  • GenericInterface<String> reverse – создает ссылку на интерфейс, который работает со String.
  • GenericInterface<Integer> factorial — создает ссылку на интерфейс, который работает с Integer.

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

В JDK8 добавлен новый пакет java.util.stream, который позволяет java-разработчикам выполнять такие операции, как поиск, фильтрация, сопоставление, объединение или манипулирование коллекциями, к примеру Lists.

Например, у нас есть поток данных (в нашем случае список строк), где каждая строка содержит название страны и ее город. Теперь мы можем обработать этот поток данных и выбрать только города Непала.

Для этого мы можем использовать комбинацию Stream API и лямбда-выражений.

Пример 6: использование лямбд в Stream API

Результат выполнения:

В приведенном выше примере обратите внимание на это выражение:

Здесь мы используем такие методы, как filter(), map(), forEach() из Stream API, которые могут принимать лямбды в качестве параметра.

Также, мы можем описать собственные выражения на основе синтаксиса, описанного выше. Это позволит нам уменьшить количество строк кода.

Related Posts