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 ismap()
, and this produces a new stream with members returned by the lambda. Another method would beparallel()
, 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 callingtoArray()
, 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 thegetName
method in classPerson
, done by the new Java 8 operator "::". That method will be called in context of thePerson
-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:
Kommentar veröffentlichen