Blog-Archiv

Freitag, 19. August 2016

Lambdas or Closures in Java

The heart of our computers are the "central processing units" (CPU). These grew faster and faster in the passed decades, but the limit seems to be reached. Nowaday's computers have multiple processors that work in parallel to improve performance. But what about all the existing software? Is it able to distribute the work among multiple processors?

Unfortunately no. Traditionally written applications are not able to efficiently use multiple processors. Their loops, which are mostly responsible for slow performance, run in a single thread. Work can be distributed among several CPUs only when the application explicitly runs multiple threads, optimally as many threads as there are CPUs.

But how can we run a loop simultaneously in multiple threads?
By using the new functional-programming features in Java 8, called "lambda" and "monad".

This Blog is about lambdas, next one will be about monads. But I won't write about parallel programming in either of them. There is also a very good introduction into lambdas on the Java tutorial page.


What Is a Lambda?

The most understandable translation I found is "anonymous function". That means, it is a function without a name. But, optionally, with parameters. And a context.

Originally, Java lambdas should have been called "closures", but finally the vendors decided for the more abstract term.

The term "continuation" is also very close, but this is more general. A continuation is something that will be executed later, be it a named class-method or a closure.

Nevertheless this explanation does not make sense for Java, because ....

We Don't Have Functions in OO

Functions do not exist in an object-oriented language. Just methods. Methods are bound to classes, and they work on objects which are instantiations of classes. A method would be a function if you could pass it around as parameter to other methods or constructors.

Can we work around this?
Yes, and that's the way how functions were implemented in Java 8:

  • create an interface,
  • put the method signature into it,
  • make some class implement the interface,
  • instantiate the class

That way you can pass the method around, packed into the object of the class implementing the interface.
Here is an example of a pre-Java-8 closure (or lambda):

        Collections.sort(
            personList(),
            new Comparator<Person>() {
                public int compare(Person person1, Person person2) {
                    return person1.getName().compareTo(person2.getName());
                }
            }
        );

The method Collections.sort() is a static implementation that sorts a List given as 1st parameter, using the Comparator object given as 2nd parameter.

Let's say the personList() method produces a list of Person objects (find source code on bottom).

The anonymous Comparator implementation makes it possible to pass the compare() method to Collections.sort() like a function.

In JavaScript it is different. Here no classes exist, and you can give a function call any context you wish. You can pass functions as parameters simply by using their name, without parentheses.

But don't believe that JS is a functional language. It is not. JS (before EcmaScript 6) does not have immutable fields (constants), and immutability is one of the most important features of a functional language. It is the admission-price for multi-threaded programming, where only functions without side-effects are safe, preferably working on immutable data, always producing new data instead of modifying the existing.

Moreover, JS is a single-threaded language, there is no synchronized keyword!

A Function with No Name?

This is completely new in Java 8, although there have been "anonymous classes" (see Comparator above) since 1.1. Here is an example for a lambda that does exactly the same as the code above:

        Collections.sort(
            personList(),
            (person1, person2) -> person1.getName().compareTo(person2.getName())
        );

The lambda is the 2nd parameter, it is the Comparator. Lambdas are mostly described by the operator "->" (new in Java 8). In Java 8, the interface Comparator has been annotated as @FunctionalInterface. That way the Java compiler accepts the lambda as Comparator implementation, without declaring any class.

(person1, person2) -> person1.getName().compareTo(person2.getName())

As you can see, this has no name, but optional parameters. The person1.getName().compareTo(person2.getName()) lambda-body will be executed any time the sort() method compares two objects while sorting the collection.

The return value of a lambda is implicitly what the single statement produces, in this case a boolean. But there are also other shapes of lambdas, having several statements. Then you need an explicit return statement.

Different Shapes

All of the following examples do the same thing. This time I will use the Stream monad, although I did not yet explain that.

In short, the collection of persons is turned into a "stream". This hides the loop over all contained persons. It is necessary to hide the loop to optionally process it multi-threaded. You can call different methods on streams, one of them is map(), and this produces a new stream with members returned by the lambda. Another method would be parallel(), which enables multi-threading. (Be sure to have no side-effects in your lambdas when using that.) You can turn a stream into an array by calling toArray(), this would also end any parallel processing.

That means, the lambda inside the map() call is executed for each person in the Person-stream, and the resulting String-stream then contains the names of these persons (the name was returned by the lambda).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
        Stream<String> names;
        
        names = personList()
            .stream()
            .map(Person::getName);  // class-bound function pointer
    
        names = personList()
            .stream()
            .map(person->person.getName());   // classic lambda
        
        names = personList()
            .stream()
            .map((Person person) -> person.getName());  // typed parameter
    
        names = personList()
            .stream()
            .map(person -> {    // more than one statement could be in braces
                return person.getName();
            });
        
        names = personList()
            .stream()
            .map(new Function<Person, String>() {   // old-style closure
                public String apply(Person person) {
                    return person.getName();
                }
            });
Person::getName
A direct reference to the getName method in class Person, done by the new Java 8 operator "::". That method will be called in context of the Person-instance given to the lambda as parameter.
person->person.getName()
Minimal shape, often seen in examples.
Without spaces, this is a misleading reminiscence of the C-language member-selection operator.
(Person person) -> person.getName()
Optionally you can give types to parameters. When not present, the compiler will deduce them.
person -> { .... }
When the lambda has more than one statement, you must enclose them into curly braces. Should it have a return, this must be explicitly given as statement.
new Function() { .... }
This is the old way how it also could be done in Java prior to 8, should it have the Function interface.
() -> ....
            // lambda with no parameters

            () -> System.out.println("Hello World")
            
If a lambda has no parameters, you must use "()" instead.

Context

What is available to a Java lambda, which fields and methods of its context can it see? Class member fields? Local outside variables? Parameters of the enclosing method?

All of them are available. Local variables and parameters of the enclosing method not even need to be final any more.

But the lambda body can not assign a new value to an outer local variable or an enclosing method parameter. These are implicitly final now. You will get a compile-error when trying such. It only can assign a new value to one of its own parameters.

But the lambda-body is allowed to assign new values to member fields of the enclosing class. Which is a contradiction to the functional principle of absent side-effects. That's life with Java :-)

Functional Interfaces

With Java 8 you always can create your own functional interfaces. Be sure that it is annotated by @FunctionalInterface, and only one method signature is contained (SAM = single abstract method), all others must be either default or static implementations, or overrides of Object methods like equals(). That single abstract method is the lambda's missing name!

Java 8 provides several functional interfaces for working with streamed lists and other monads, the most important being ....

Example Source Code

Click here to expand the example source code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/**
 * Different ways to write lambdas.
 */
public class LambdaShapes
{
    public static void main(String[] args) {
        new LambdaShapes();
    }
    
    LambdaShapes() {
        Collections.sort(
            personList(),
            new Comparator<Person>() {
                public int compare(Person person1, Person person2) {
                    return person1.getName().compareTo(person2.getName());
                }
            }
        );
            
        Collections.sort(
            personList(),
            (person1, person2) -> person1.getName().compareTo(person2.getName())
        );
        
        Stream<String> names;
        
        names = personList()
            .stream()
            .map(Person::getName);  // class-bound function pointer
    
        names = personList()
            .stream()
            .map(person->person.getName());   // classic lambda
        
        names = personList()
            .stream()
            .map((Person person) -> person.getName());  // typed parameter
    
        names = personList()
            .stream()
            .map(person -> {    // more than one statement could be in braces
                return person.getName();
            });
        
        names = personList()
            .stream()
            .map(new Function<Person, String>() {   // old-style closure
                public String apply(Person person) {
                    return person.getName();
                }
            });
    }
    
    private List<Person> personList()    {
        final List<Person> persons = new ArrayList<>();
        persons.add(new Person("Tim"));
        persons.add(new Person("Jack"));
        persons.add(new Person("Joshua"));
        return persons;
    }
    

    private static class Person
    {
        private final String name;
        
        public Person(String name) {
            this.name = name;
        }
        
        public String getName() {
            return name;
        }
    }
    
}



Keine Kommentare: