JavaRush /Java Blog /Random EN /Lambda Expressions with Examples
Author
Aditi Nawghare
Инженер-программист at Siemens

Lambda Expressions with Examples

Published in the Random EN group
Java is initially a completely object-oriented language. With the exception of primitive types, everything in Java is an object. Even arrays are objects. Instances of each class are objects. There is not a single possibility to define a function separately (outside a class - approx. transl. ). And there is no way to pass a method as an argument or return a method body as the result of another method. It's like that. But this was before Java 8. Lambda expressions with examples - 1Since the days of good old Swing, it was necessary to write anonymous classes when it was necessary to pass some functionality to some method. For example, this is what adding an event handler looked like:

someObject.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                 
                //Event listener implementation goes here...
                 
            }
        });
Here we want to add some code to the mouse event listener. We defined an anonymous class MouseAdapterand immediately created an object from it. In this way, we have passed additional functionality into the addMouseListener. In short, it is not easy to pass a simple method (functionality) in Java through arguments. This limitation forced the Java 8 developers to add a feature such as Lambda expressions to the language specification.

Why do Java need Lambda expressions?

From the very beginning, the Java language has not evolved much, except for things like Annotations, Generics, etc. First of all, Java has always remained object-oriented. After working with functional languages ​​such as JavaScript, one can understand how Java is strictly object-oriented and strongly typed. Functions are not needed in Java. By themselves, they cannot be found in the Java world. In functional programming languages, functions come to the fore. They exist on their own. You can assign them to variables and pass them through arguments to other functions. JavaScript is one of the best examples of functional programming languages. You can find good articles on the Internet that detail the benefits of JavaScript as a functional language. Functional languages ​​have powerful tools such as Closure, which provide a number of advantages over traditional methods of writing applications. A closure is a function with an environment attached to it - a table that stores references to all non-local variables of the function. In Java, closures can be simulated through Lambda expressions. Of course, there are differences between closures and Lambda expressions, and not small ones, but lambda expressions are a good alternative to closures. In his sarcastic and funny blog, Steve Yegge describes how the world of Java is strictly tied to nouns (entities, objects - approx. transl. ). If you haven't read his blog, I recommend it. He describes in a funny and interesting way the exact reason why Lambda expressions were added to Java. Lambda expressions bring functionality to Java that has been missing for so long. Lambda expressions bring functionality to the language just like objects. While this is not 100% true, you can see that Lambda expressions, while not being closures, provide similar capabilities. In a functional language, lambda expressions are functions; but in Java, lambda expressions are represented by objects, and must be associated with a specific object type called a functional interface. Next we will look at what it is. Mario Fusco's article “Why we need Lambda Expression in Java” describes in detail why all modern languages ​​need closure capabilities.

Introduction to Lambda Expressions

Lambda expressions are anonymous functions (may not be a 100% correct definition for Java, but it brings some clarity). Simply put, this is a method without declaration, i.e. without access modifiers, returning value and name. In short, they allow you to write a method and use it immediately. It is especially useful in the case of a one-time method call, because reduces the time it takes to declare and write a method without having to create a class. Lambda expressions in Java typically have the following syntax (аргументы) -> (тело). For example:

(арг1, арг2...) -> { тело }
 
(тип1 арг1, тип2 арг2...) -> { тело }
Below are some examples of real Lambda expressions:

(int a, int b) -> {  return a + b; }
 
() -> System.out.println("Hello World");
 
(String s) -> { System.out.println(s); }
 
() -> 42
 
() -> { return 3.1415 };

Structure of Lambda Expressions

Let's study the structure of lambda expressions:
  • Lambda expressions can have 0 or more input parameters.
  • The type of parameters can be specified explicitly or can be obtained from the context. For example ( int a) can be written like this ( a)
  • Parameters are enclosed in parentheses and separated by commas. For example ( a, b) or ( int a, int b) or ( String a, int b, float c)
  • If there are no parameters, then you need to use empty parentheses. For example() -> 42
  • When there is only one parameter, if the type is not specified explicitly, the parentheses can be omitted. Example:a -> return a*a
  • The body of a Lambda expression can contain 0 or more expressions.
  • If the body consists of a single statement, it may not be enclosed in curly braces, and the return value may be specified without the keyword return.
  • Otherwise, the curly braces are required (block of code) and the return value must be specified at the end using a keyword return(otherwise the return type will be void).

What is a functional interface

In Java, Marker interfaces are interfaces without declaring methods or fields. In other words, token interfaces are empty interfaces. Similarly, Functional Interfaces are interfaces with only one abstract method declared on it. java.lang.Runnableis an example of a functional interface. It only declares one method void run(). There is also an interface ActionListener- also functional. Previously, we had to use anonymous classes to create objects that implement a functional interface. With Lambda expressions, everything has become simpler. Each lambda expression can be implicitly bound to some functional interface. For example, you can create a reference to Runnablean interface, as shown in the following example:

Runnable r = () -> System.out.println("hello world");
This kind of conversion is always done implicitly when we don't specify a functional interface:

new Thread(
    () -> System.out.println("hello world")
).start();
In the example above, the compiler automatically creates a lambda expression as an implementation Runnableof the interface from the class constructor Thread: public Thread(Runnable r) { }. Here are some examples of lambda expressions and corresponding functional interfaces:

Consumer<Integer> c = (int x) -> { System.out.println(x) };
 
BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);
 
Predicate<String> p = (String s) -> { s == null };
The annotation @FunctionalInterfaceadded in Java 8 according to the Java Language Specification checks whether the declared interface is functional. In addition, Java 8 includes a number of ready-made functional interfaces for use with Lambda expressions. @FunctionalInterfacewill throw a compilation error if the declared interface is not functional. The following is an example of defining a functional interface:

@FunctionalInterface
public interface WorkerInterface {
 
    public void doSomeWork();
 
}
As the definition suggests, a functional interface can have only one abstract method. If you try to add another abstract method, you will get a compilation error. Example:

@FunctionalInterface
public interface WorkerInterface {
 
    public void doSomeWork();
     
    public void doSomeMoreWork();
 
}
Error

Unexpected @FunctionalInterface annotation 
    @FunctionalInterface ^ WorkerInterface is not a functional interface multiple 
    non-overriding abstract methods found in interface WorkerInterface 1 error
После определения функционального интерфейса, мы можем его использовать и получать все преимущества Lambda-выражений. Пример:// defining a functional interface
@FunctionalInterface
public interface WorkerInterface {
 
    public void doSomeWork();
 
}

public class WorkerInterfaceTest {
 
    public static void execute(WorkerInterface worker) {
        worker.doSomeWork();
    }
 
    public static void main(String [] args) {
 
      // calling the doSomeWork method via an anonymous class
      // (classic)
      execute(new WorkerInterface() {
            @Override
            public void doSomeWork() {
               System.out.println("Worker called via an anonymous class");
            }
        });
     
      // calling the doSomeWork method via Lambda expressions
      // (Java 8 new)
      execute( () -> System.out.println("Worker called via Lambda") );
    }
 
}
Conclusion:

Worker вызван через анонимный класс
Worker вызван через Lambda
Here we have defined our own functional interface and used a lambda expression. The method execute()is capable of accepting lambda expressions as an argument.

Examples of Lambda expressions

The best way to understand Lambda expressions is to look at a few examples: A stream Threadcan be initialized in two ways:

// Old way:
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from thread");
    }
}).start();

// New way:
new Thread(
    () -> System.out.println("Hello from thread")
).start();
Event management in Java 8 can also be done through Lambda expressions. The following are two ways to add an event handler ActionListenerto a UI component:

// Old way:
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button pressed. Old way!");
    }
});

// New way:
button.addActionListener( (e) -> {
        System.out.println("Button pressed. Lambda!");
});
A simple example of displaying all elements of a given array. Note that there is more than one way to use a lambda expression. Below we create a lambda expression in the usual way using arrow syntax, and we also use the double colon operator (::), which in Java 8 converts a regular method into a lambda expression:

// Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
    System.out.println(n);
}

// New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));

// New way using double colon operator ::
list.forEach(System.out::println);
In the following example, we use a functional interface Predicateto create a test and print items that pass that test. This way you can put logic into lambda expressions and do things based on it.

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
 
public class Main {
   
    public static void main(String [] a)  {
 
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
 
        System.out.print("Outputs all numbers: ");
        evaluate(list, (n)->true);
 
        System.out.print("Does not output any number: ");
        evaluate(list, (n)->false);
 
        System.out.print("Output even numbers: ");
        evaluate(list, (n)-> n%2 == 0 );
 
        System.out.print("Output odd numbers: ");
        evaluate(list, (n)-> n%2 == 1 );
 
        System.out.print("Output numbers greater than 5: ");
        evaluate(list, (n)-> n > 5 );
 
    }
 
    public static void evaluate(List<Integer> list, Predicate<Integer> predicate) {
        for(Integer n: list)  {
            if(predicate.test(n)) {
                System.out.print(n + " ");
            }
        }
        System.out.println();
    }
 
}
Conclusion:

Выводит все числа: 1 2 3 4 5 6 7 
Не выводит ни одного числа: 
Вывод четных чисел: 2 4 6 
Вывод нечетных чисел: 1 3 5 7 
Вывод чисел больше 5: 6 7
By tinkering with Lambda expressions, you can display the square of each element of the list. Notice that we are using the method stream()to convert a regular list into a stream. Java 8 provides an awesome class Stream( java.util.stream.Stream). It contains tons of useful methods that you can use lambda expressions with. We pass a lambda expression x -> x*xto the method map(), which applies it to all elements in the stream. After which we use forEachto print all the elements of the list.

// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
    int x = n * n;
    System.out.println(x);
}

// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x) -> x*x).forEach(System.out::println);
Given a list, you need to print the sum of the squares of all elements of the list. Lambda expressions allow you to achieve this by writing just one line of code. This example uses the convolution (reduction) method reduce(). We use a method map()to square each element, and then use a method reduce()to collapse all elements into a single number.

// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = 0;
for(Integer n : list) {
    int x = n * n;
    sum = sum + x;
}
System.out.println(sum);

// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();
System.out.println(sum);

The difference between Lambda expressions and anonymous classes

The main difference is the use of the keyword this. For anonymous classes, the ' ' keyword thisdenotes an object of the anonymous class, while in a lambda expression, ' this' denotes an object of the class in which the lambda expression is used. Another difference is the way they are compiled. Java compiles lambda expressions and converts them into privateclass methods. This uses the invokedynamic instruction , introduced in Java 7 for dynamic method binding. Tal Weiss described in his blog how Java compiles lambda expressions into bytecode

Conclusion

Mark Reinhold (Oracle's Chief Architect) called Lambda expressions the most significant change in the programming model that has ever occurred - even more significant than generics. He must be right, because... they give Java programmers the functional programming language capabilities everyone has been waiting for. Along with innovations such as Virtual extension methods, Lambda expressions allow you to write very high-quality code. I hope this article gave you a look under the hood of Java 8. Good luck :)
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION