Java 8 first steps with Lambdas and Streams

8 Comments

It’s coming soon: According to Oracle the new version Java 8 will be released in March 2014.  This release will bring the largest changes on the platform since the introduction of Generics in version 1.5. It’s on the time to look at some of the most important innovations.

One of the amazing new features the Java community is waiting for is the introduction of Lambdas (e.g. Closures) . Let’s skip the theory and look at some examples of what we can do with them.

Since Java 1.5 we are used to the ‘extended for loop’ whenever we want to iterate over the elements of a Collection:

List myList = Arrays.asList("element1","element2","element3");
for (String element : myList) {
  System.out.println (element);
}

This code is reasonably short and clear. Unfortunately there is a big disadvantage. It is really hart to execute the operation on the elements in parallel. Imagine we need to iterate through the elements of a large list and it is required to use multiple threads to reach the desired performance goals. What we need to do is to split the list into several lists and handle the threads to operate on them. Even though we can get some support from the fork join framework it might be a really difficult job.

Wouldn’t it be nice to have a List implementation doing this job for us?

Exactly for this use case the Iterable Interface has been extended in Java 8. With the new forEach method we are able to use an implementation like this:

myList.forEach(new Consumer() {
   public void accept(String element) {
      System.out.println(element);
   }
});

Although this code is much longer and looks even more complicated it has a big advantage. The logic to iterate through the elements of the list and the logic executed on the elements of the list has been cleanly separated. The respective implementation of the forEach method is now capable of the control to iterate over the elements and can for example create multiple threads for us.

However we have a much more complex code now. At this point Lambda expressions come into play. As Consumer is a so called FunctionalInterface we can simplify the above code by using a Lambda:

myList.forEach((String element) -> System.out.println(element));

In this special case we can even more simplify the command because element is the only parameter. This makes it possible to implicitly determine the type of the parameter:

myList.forEach(element -> System.out.println(element));

A detailed description of the formal Lambda syntax is out of scope for this article. For those of you interested in getting more information about the topic I  recommend  the corresponding Java Tutorial, as well as the Lambda Quick Start.

But wait! – The interface Iterable has been extended with new methods?
Does this mean all my own implementations implementing this interface will not be compatible with Java 8 anymore?

Fortunately not. Because another improvement of Java 8 introduces „default“ implementations of methods within interfaces.

default void forEach(Consumer<? super T> action) {
   Objects.requireNonNull(action);
   for (T t : this) {
       action.accept(t);
   }
}

 

The above code is the default implementation of the new  forEach method in Iterable. As you can see it just makes use of the extendet for loop to iterate over the elements and executes the logic defined in the passed Consumer.

But now we are facing another problem according to default implementations in interfaces:
What will happen, if we design a new class implementing two different interfaces with different default implementation for the same method?

public interface Int1 {
     default String doSomething () {
        return "Int1.doSomething";
     }
}
public interface Int2 {
     default String doSomething ()  {
        return "Int2.doSomething");
     }
}
public class MyClass implements Int1, Int2 { }

 

Such a construct will inevitably lead to an error. It is not possible to compile this code:

 

MyClass.java:11: error: 
class MyClass inherits unrelated defaults for doSomething() from types Int1 and Int2

 

The solution is simple. We just have to explicitly solve the conflict by overriding the ambiguous method doSomething() in MyClass:

public class MyClass implements Int1, Int2 {
    public String doSomething() {
        return Int1.super.doSomething();
    }
}

 

So the common apprehension the default mechanism will introduce multiple inheritance to the Java language seems to be unfounded.

Especially the Java 8 Collection Framework already makes extensive use of the new default implementations. In addition to the already shown  forEach() method in interface Iterable there’s for example another extension in the Collection Interface introducing stream support by providing methods like stream() and parallelStream():

default Stream stream() {
   return StreamSupport.stream(spliterator(), false);
}

Streams enable the user to combine commands in some sort of pipeline. A Stream does not store any elements. It is not a data structure. It just operates on the underlying data structure without modifying it. In addition to more readable code we gain a much better way to execute operations in parallel. Let’s assume we want to count the elements of a list fitting a criteria:

Collection myList = Arrays.asList("Hello","Java");
long countLongStrings = myList.stream().filter(new Predicate() {
          @Override
          public boolean test(String element) {
              return element.length() > 4;
          }
}).count();

Ok, right. This is not very clear nor readable. You have to read a lot of code and spend some time to find out what requirement is implemented with this code. But fortunately Lambdas are available:

 

Collection myList = Arrays.asList("Hello","Java");
long countLongStrings = myList.stream().filter(element -> element.length() > 4).count();

 

This code is already better. It’s much easier to get to the requirement (count all elements with more than 4 characters) and the boilerplate code to iterate over the collection does not interfere the readability anymore.
Another advantage of the second approach is, that the compiler does not need to generate an additional inner class when using a Lambda expression. Looking at the javac output after compiling the first code snippet we see two separate class files:

 

ForEach$1.class        ForEach.class

 

After changing the code snippet and using a Lambda expression the ForEach$1.class file disappears. This is due to the fact that Lambda expressions make use of the „invoke dynamic“ feature introduced in Java 7.

Let’s have a closer look at Streams:
Stream.filter() is one of the so called “intermediate operations”. This kind of operations return a new Stream (stream-producing), which we can directly use to call other Stream operations. Other examples of intermediate operations are:

  • map()
  • sorted()
  • unsorted()
  • distinct()
  • limit()
  • peek().

In contrary to the intermediate operations the method count() is a “terminal operation“. Terminal means the operation forms the end of the stream. It is always the last operation and ends the pipeline by returning a value (value-producing).
Other examples for terminal operations are:

  • sum()
  • min()
  • max()
  • reduce()
  • findFirst()

 

In addition to Lambdas and Streams there are several more innovations introduced with Java 8, just follow our blog frequently for more topics to come. Some of the main features from my perspective are the new Date and Time API, JavaScript Integration (Project Nashorn) as well as the removal of the Permanent Generation in the Hotspot VM.

Author

Lars Rückemann

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentare

Comment

Your email address will not be published. Required fields are marked *