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. Since 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 MouseAdapter
and 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 bevoid
).
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.Runnable
is 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 Runnable
an 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 Runnable
of 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 @FunctionalInterface
added 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. @FunctionalInterface
will 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 streamThread
can 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 ActionListener
to 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 Predicate
to 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*x
to the method map()
, which applies it to all elements in the stream. After which we use forEach
to 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 keywordthis
. For anonymous classes, the ' ' keyword this
denotes 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 private
class 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
GO TO FULL VERSION