Sep 26, 2013

Lambdas and Streams in JDK 8

Lambdas and Streams at the JavaOne 2013

Lambda

The new Lamda expressions in combindation with the Stream-API in Java 8 leads to the biggest change in Java Programming since the introduction of Generics in JDK 5.
Labmda Functions remove tons of boilerplate code from anonymous inner classes and make the code more compact and readable.  

The following example shows typical UI code which is needed for JavaFX in JDK 7.

 button.setOnAction(new EventHandler<ActionEvent>() {

      @Override

      public void handle(ActionEvent actionEvent) {

          ...      
      }

    });

In JDK 8 the same looks like this:

button.setOnAction(event -> {

       ...
});
The JDK has tons of new Methods even in existing interfaces like java.util.Collection which use Lambda functions. To make that possible, you can now provide a default implementations of methods in interfaces, so it is now possible to add new methods to an existing interface without breaking every implementor. The following example shows the forEach method of the java.util.Collection interface.


List<Person> persons = ...
persons.forEach(p -> p.setLastName("Doe"));

But thats all syntactic suger. But whats really makes a difference in daily programming is the new Stream API.

Streams != java.io

What is a Stream? You can think of an iterator, which can access a real collection but also think of generated data like primes or other endless sequences. 
You can filter, map, reduce, split streams and form other streams with other content or even populate new collections from a stream. Here are some examples:

1. Compute a list of adults from a collection of persons.



List<Person> greater18 = persons.stream()
        .filter(p -> p.getAge() >= 18)
        .collect(toList());

The filtering is lazy. It only executes when the stream is consumed (by the collect(toList()) method. The filter/collect methods are methods from Stream itself. It is a fluent API design.


2. Compute a set of ages of adults from a collection of persons.


Set<Integer> ages = persons.stream()
        .filter(p -> p.getAge() >= 18)
        .map(Person::getAge)
        .collect(toSet());

The new thing here is we map a Person to an Integer (the age) by referencing the getAge method. It looks a little like C++. It is legal to use existing functions as lamdas. 

3. Compute the population per age


Map<Integer, Long> population.perAge = persons.stream()
        .collect(groupingBy(Person::getAge, counting()));

The map stores the age and maps the count of people.

4. Compute the names per age


List<Person> persons = ...
Map<String, Long> population.perAge = persons.stream()
        .collect(groupingBy(Person::getAge, mapping(Person::getName, toList()));

It really makes programming different when you think of normal collection based programming. Java Programms will look like never before.

BUT: Not in every case lamdas leed to better readable and maintainable code. The following example (shown here at the JavaOne as valid example) is a abuse. 



 List<Person> pa = Arrays.asList(new Person("Peter"), new Person("Ken"));

 // a) 
 for (int i = 0; i < pa.size(); i++) {
    Person p = pa.get(i);
    if (p.getName().equals("Ken")) {
        doSomething(p);
    }
 }

 // b) Exact the same !!!
 IntStream.range(0, pa.size())
     .mapToObj(pa::get)
     .filter(p -> p.getName().equals("Ken"))
     .forEach(Main::doSomething);


The section b) is a exact port of the imperativ version with a functional syntax. That does not make sense. The example is hypothetic but if programmers are tought lamdas are cool in every way - this can be the result. 
In terms of language design it is a problem when a language allows to many different ways to express the same. C++ is such a problematic example. The complexity lead to the development of Java...

Make sure you have read the pragmatic programmer before you get lost in <<>>, -> or :: symbols. 

Johannes Weigend


No comments:

Post a Comment