Java 8 brought many new features known up until now mainly from functional programming languages. Among the most interesting ones, there are lambda expressions, default methods, stream API, etc. In this post, let me introduce you to the first of listed — lambda expressions.
What are lambda expressions?
Simply put, lambda expressions are blocks of code with parameters. Until now, it wasn’t easy to just pass around a block of code in Java. As Java is an object-oriented language, one had to construct an instance of a class containing a method with the code first. With the changes brought by Java 8 however, lambda expressions can be used in place of anonymous instances of anonymous classes which used to be the usual practice.
Let me show you an example you’ve surely come across before:
button.setOnAction(new EventHandler<ActionEvent>() { public void handle(ActionEvent event) { System.out.println("Button clicked!"); } });
This basic example shows a common way of declaration of a button callback. You specify the callback action inside a method of a class implementing the listener interface, then construct an instance of the class, and register it with the button. The code inside the handle method is then executed whenever the button is clicked.
Another common example lambda expressions can be used in is when you want to run a block of code in a separate thread. You can put the code into the run method of a Runnable class instance and then start the thread, like this:
Thread thread = new Thread(new Runnable() { @Override public void run() { doWork(); } }); thread.start();
Let’s have a look at how to replace anonymous instances of classes with simple and elegant lambda expressions.
How to use lambda expressions?
Continuing with the two examples from the previous section, let’s rewrite them using lambda expressions. The button callback can be rewritten like this:
button.setOnAction((ActionEvent event) -> System.out.println("Button clicked!"));
The general syntax of lambda expressions in Java is: parameters in parenthesis, the -> operator, and an expression. If the code cannot fit in a single expression, it has to be enclosed in {} and with explicit return statement. A Comparator implementation example follows:
Comparator<String> comp = (String first, String second) -> { if (first.length() < second.length()) return -1; else if (first.length() > second.length()) return 1; else return 0; }
If the type of parameters can be inferred, the type declaration can be omitted. However, you still have to supply empty parentheses even if the lambda expression has no parameters like in case of a rewritten second example from previous section:
Thread thread = new Thread(() -> doWork()); thread.start();
If a method has a single parameter with its type inferred, in this case even the parentheses can be omitted:
button.setOnAction(event -> System.out.println("Button clicked!"));
However, the result type of a lambda expression is always inferred, and you cannot explicitly specify it.
More on lambda expressions
Apart from the presented usage of lambda expressions, Java 8 provides several other language features and existing code enhancements we shall describe further.
Method references
There are cases when a fairly simple lambda expression can be simplified even more. One of the ways how to simplify the expression is to use a method reference. For example if the body of a lambda expression only consists of an class method call, it can be replaced with a method reference like this:
Arrays.sort(new String[] {"b", "a", "c"}, (first, second) -> first.compareToIgnoreCase(second));
Arrays.sort(new String[] {"b", "a", "c"}, String::compareToIgnoreCase);
The parameters are automatically supplied to match the method signature.
Three cases of the :: operator usage exist:
- object::instanceMethod
- Class::staticMethod
- Class::instanceMethod
Furthermore, this and super references can be captured and used in method referencing.
Constructor references
Similarly to the method references, constructor references can be used. Constructor references refer to a constructor of a class it has been used on. Which of the possible multiple constructors is selected depends on the current context.
To demonstrate, the second line of the following code maps the Integer(String) constructor on to each element of list of strings and afterwards adds them into a new collection using a collector (the code uses some constructions which will be explained in one of the future posts):
List<String> listOfStrings = Arrays.asList("1","2","3"); List<Integer> listOfInts = listOfStrings.stream().map(Integer::new).collect(Collectors.toList());
Enhaced collection looping
By the addition of lambda expressions into Java 8, other parts of the language got improved as well. One of the examples of such an enhancement is the collection looping. Newly a forEach method is available to all the collections in Java language allowing to iterate though any already existing collection. It can be used with any previously described lambda expression or method reference.
A simple code printing out each element of a list follows as an example:
List<String> list = Arrays.asList("a", "b", "c"); list.forEach(System.out::println);
Conclusion
In this post, lambda expressions brought by Java 8 have been introduced together with several new features and enhancements to the language. In the following post, I’ll explain how to use the convenience of using lambda expressions in Android development using Java 8 as well as older releases. Interested in more Java 8 language features? Read my post on Stream API in Java 8.
Sources
- Cay S. Horstmann. Java SE8 for the Really Impatient
- Oracle. Java SE 8: Lambda Quick Start
- Maurice Naftalin. Maurice Naftalin’s Lambda FAQ