Shahzad Bhatti

April 23, 2014

Implementing Reactive Extensions (RX) using Java 8

Filed under: Java — admin @ 11:52 pm

In my last blog, I described new lambda support in Java 8. In order to try new Java features in more depth, I implemented Reactive extensions in Java 8. In short, reactive extensions allows processing synchronous and asynchronous in data uniform manner. It provides unified interfaces that can be used as an iterator or callback method for asynchronous processing. Though, Microsoft RX library is huge but I only implemented core features and focused on Observable API. Here is brief overview of implementation:

Creating Observable from Collection

Here is how you can create Observable from a collection:

    List<String> names = Arrays.asList("One", "Two", "Three", "Four", "Five"); 
    Observable.from(names).subscribe(System.out::println, 
       Throwable::printStackTrace, () -> System.out.println("done"));
 

In Microsoft’s version of RX, Observable takes an Observer for subscription, which defines three methods: onNext, onError and onCompleted. onNext is invoked to push next element of data, onError is used to notify errors and onCompleted is called when data is all processed. In my implementation, the Observable interface defines two overloaded subscribe method, first takes callback functions for onNext and onError and second method takes three callback functions including onCompleted. I chose to use separate function parameters instead of a single interface so that caller can pass inline lambda functions instead of passing implementation of Observer interface.

Creating Observable from Array of objects

Here is how you can create Observable from stream:

    Observable.from("Erica", "Matt", "John", "Mike").subscribe(System.out::println, 
         Throwable::printStackTrace, () -> System.out.println("done"));
 

Creating Observable from Stream

Here is how you can create Observable from stream:

    Stream<String> names = Stream.of("One", "Two", "Three", "Four", "Five"); 
    // note third argument for onComplete is optional
    Observable.from(names).subscribe(name -> System.out.println(name), 
       error -> error.printStackTrace());
 

Creating Observable from Iterator

Here is how you can create Observable from iterator:

    Stream<String> names = Stream.of("One", "Two", "Three", "Four", "Five"); 
    Observable.from(names.iterator()).subscribe(name -> System.out.println(name), 
       error -> error.printStackTrace());
 

Creating Observable from Spliterator

Here is how you can create Observable from spliterator:

    List<String> names = Arrays.asList("One", "Two", "Three", "Four", "Five"); 
    Observable.from(names.spliterator()).subscribe(System.out::println, 
       Throwable::printStackTrace);
 

Creating Observable from a single object

Here is how you can create Observable from a single object:

    Observable.just("value").subscribe(v -> System.out.println(v), 
       error -> error.printStackTrace());
    // if a single object is collection, it would be treated as a single entity, e.g.
    Observable.just(Arrays.asList(1, 2, 3)).subscribe( num -> System.out.println(num), 
       error -> error.printStackTrace());
 

Creating Observable for an error

Here is how you can create Observable that would return an error:

    Observable.throwing(new Error("test error")).subscribe(System.out::println, 
       error -> System.err.println(error));
    // this will print error 
 

Creating Observable from a consumer function

Here is how you can create Observable that takes user function for invoking onNext, onError and onCompleted function:

    Observable.create(observer -> {
       for (String name : names) {
          observer.onNext(name);
       }
       observer.onCompleted();
    }).subscribe(System.out::println, Throwable::printStackTrace);
 

Creating Observable from range

Here is how you can create Observable from stream that would create numbers from start to end range exclusively.

    // Creates range of numbers starting at from until it reaches to exclusively
    Observable.range(4, 8).subscribe(num -> System.out.println(num), 
       error -> error.printStackTrace());
    // will print 4, 5, 6, 7
 

Creating empty Observable

It would call onCompleted right away:

    Observable.empty().subscribe(System.out::println, 
       Throwable::printStackTrace, () -> System.out.println("Completed"));
 

Creating never Observable

It would not call any of call back methods:

    Observable.never().subscribe(System.out::println, Throwable::printStackTrace);
 
 

Changing Scheduler

By default Observable notifies observer asynchronously using thread-pool scheduler but you can change default scheduler as follows:

Using thread-pool scheduler

    Stream<String> names = Stream.of("One", "Two", "Three", "Four", "Five"); 
    Observable.from(names).subscribeOn(Scheduler.getThreadPoolScheduler()).
       subscribe(System.out::println, Throwable::printStackTrace);
 

Using new-thread scheduler

It will create new thread

    Stream<String> names = Stream.of("One", "Two", "Three", "Four", "Five"); 
    Observable.from(names).subscribeOn(Scheduler.getNewThreadScheduler()).
       subscribe(System.out::println, Throwable::printStackTrace);
 

Using timer thread with interval

It will notify at each interval

    Stream<String> names = Stream.of("One", "Two", "Three", "Four", "Five"); 
    Observable.from(names).subscribeOn(Scheduler.getTimerSchedulerWithMilliInterval(1000)).
       subscribe(System.out::println, Throwable::printStackTrace);
    // this will print each name every second
 

Using immediate scheduler

This scheduler calls callback functions right away on the same thread. You can use this if you synchronous data and don’t want to create another thread. On the downside, you cannot unsubscribe with this scheduler.

    Stream<String> names = Stream.of("One", "Two", "Three", "Four", "Five"); 
    Observable.from(names).subscribeOn(Scheduler.getImmediateScheduler()).
       subscribe(System.out::println, Throwable::printStackTrace);
 

Transforming

Observables keep sequence of items as streams and they support map/flatMap operation as supported by standard Stream class, e.g.

Map

    Stream<String> names = Stream.of("One", "Two", "Three", "Four", "Five"); 
    Observable.from(names).map(name -> name.hashCode()).
       subscribe(System.out::println, Throwable::printStackTrace);
 

FlatMap

    Stream integerListStream = Stream.of( Arrays.asList(1, 2), 
       Arrays.asList(3, 4), Arrays.asList(5));
    Observable.from(integerListStream).flatMap(integerList -> integerList.stream()).
       subscribe(System.out::println, Throwable::printStackTrace);
 

Filtering

Observables supports basic filtering support as provided by Java Streams, e.g.

Filter

    Stream<String> names = Stream.of("One", "Two", "Three", "Four", 
       "Five"); 
    Observable.from(names).filter(name -> name.startsWith("T")).
       subscribe(System.out::println, Throwable::printStackTrace);
    // This will only print Two and Three
 

Skip

skips given number of elements

    Stream<String> names = Stream.of("One", "Two", "Three", "Four", "Five"); 
    Observable.from(names).skip(2).subscribe(System.out::println, 
       Throwable::printStackTrace);
    // This will skip One and Two
 

Limit

    Stream<String> names = Stream.of("One", "Two", "Three", "Four", "Five"); 
    Observable.from(names).limit(2).subscribe(System.out::println, 
       Throwable::printStackTrace);
    // This will only print first two strings
 

Distinct

    Stream<String> names = Stream.of("One", "Two", "Three", "One");
    Observable.from(names).distinct.subscribe(System.out::println, 
       Throwable::printStackTrace);
    // This will print One only once
 

Merge

This concates two observable data:

    Observable<Integer> observable2 = Observable.from(Stream.of(4, 5, 6));
    observable1.merge(observable2).subscribe(System.out::println, 
       Throwable::printStackTrace);
    // This will print 1, 2, 3, 4, 5, 6
 

Summary

In summary, as Java 8 already supported a lot of functional primitives, adding support for reactive extensions was quite straight forward. For example, Nextflix’s implementation of reactive extensions in Java consists of over 80K lines of code but it took few hundred lines to implement core features with Java 8. You can download or fork the code from https://github.com/bhatti/RxJava8.


April 17, 2014

Introduction to Java 8 Lambda and Stream Syntax

Filed under: Java — admin @ 11:10 pm

Introduction

Java 8 was released in March 2014 with most language-level enhancements since Java 5 back in 2004. The biggest new feature is introduction to Lambda. Lambda or Closur is a block of code that you can pass to other methods or return from methods. Previously, Java supported a form of closure via anonymous class syntax, e.g.

 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.Dimension;
 import java.awt.FlowLayout;
 import javax.swing.JButton;
 import javax.swing.JFrame;
 
 public class SwingExample extends JFrame {
   public SwingExample() {
     this.getContentPane().setLayout(new FlowLayout());
     final JButton btn = new JButton("Click Me");
     btn.setPreferredSize(new Dimension(400,200));
     add(btn);
     btn.addActionListener(new ActionListener() {
       public void actionPerformed(ActionEvent e) {
         btn.setText("Clicked");
         btn.setEnabled(false);
       }
     });
   }
 
   private static void createAndShowGUI() {
     JFrame frame = new SwingExample();
     frame.pack();
     frame.setVisible(true);
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
   }
 
   public static void main(String[] args) {
     javax.swing.SwingUtilities.invokeLater(new Runnable() {
       public void run() {
         createAndShowGUI(); 
       }
     });
   }
 }
 

In above example, using anonymous class could use locally defined data in method that declares it as long as it is defined with final. Here is how the example looks like with Java 8 syntax:

 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.Dimension;
 import java.awt.FlowLayout;
 import javax.swing.JButton;
 import javax.swing.JFrame;
 
 public class SwingExample8 extends JFrame {
   public SwingExample8() {
     this.getContentPane().setLayout(new FlowLayout());
     JButton btn = new JButton("Click Me");
     btn.setPreferredSize(new Dimension(400,200));
     add(btn);
     btn.addActionListener(e -> {
         btn.setText("Clicked");
         btn.setEnabled(false);
       }
     );
   }
 
   private static void createAndShowGUI() {
     JFrame frame = new SwingExample8();
     frame.pack();
     frame.setVisible(true);
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
   }
 
   public static void main(String[] args) {
     javax.swing.SwingUtilities.invokeLater(new Runnable() {
       public void run() {
         createAndShowGUI(); 
       }
     });
   }
 }
 

As you can see, lambda syntax is very minimal. In addition, lambda syntax doesn’t require that you declare externally accessible data as final, though it cannot be changed. Java lambda also adds type inferencing so that you don’t have to define types of arguments. The lambda features are implemented using “invokedynamic” instruction to dispatch method calls, which was added in Java 7 to support dynamic languages. For example, let’s take a simple example:

 public class Run8 {
   public static void main(String[] args) {
     Runnable r = () -> System.out.println("hello there");
     r.run();
   }
 }
 

If you decompile it using

 javap -p Run8
 

You will see, it generated lambda$main$0 method, e.g.

 public class Run8 {
   public Run8();
   public static void main(java.lang.String[]);
   private static void lambda$main$0();
 }
 

You can see real byte code using

 javap -p -c Run8
 

and you will see new byte codes:

 public class Run8 {
   public Run8();
     Code:
        0: aload_0       
        1: invokespecial #1                  // Method java/lang/Object
        4: return        
 
   public static void main(java.lang.String[]);
     Code:
        0: invokedynamic #2,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
        5: astore_1      
        6: aload_1       
        7: invokeinterface #3,  1            // InterfaceMethod java/lang/Runnable.run:()V
       12: return        
 
   private static void lambda$main$0();
     Code:
        0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        3: ldc           #5                  // String hello there
        5: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        8: return        
 }
 

This means lambdas don’t have to keep reference of enclosing class and “this” inside lambda does not create new scope.

Types of Functions

Java 8 provides predefined functions (See http://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html), but there are four major types:

                 Supplier: () -> T
                 Consumer: T -> ()
                 Predicate: T -> boolean
                 Function: T -> R
 

The supplier method takes not arguments and produces an object, the consumer takes an argument for consumption, predicate evaluates given argument by returning true/false and function maps an argument of type T and returns an object of type R.

Supplier example

 import java.util.function.*;
 
 public class Supply8 {
   public static void main(String[] args) {
     Supplier<Double>> random1 = Math::random;
     System.out.println(random1.get());
     //
     DoubleSupplier random2 = Math::random;
     System.out.println(random2.getAsDouble());
   }
 }
 

Note: Java 8 provides special functions for primitive types that you can use instead of using wrapper classes for primitive types. Here is another example that shows how you can write efficient log messages:

 import java.util.function.*;
 
 public class SupplyLog {
   private static boolean debugEnabled; 
   public static void debug(Supplier<String> msg) {
     if (debugEnabled) {
       System.out.println(msg.get());
     }
   }
   public static void main(String[] args) {
     debug(() -> "this will not be printed");
     debugEnabled = true;
     debug(() -> "this will be printed");
   }
 }
 

Consumer example

 import java.util.function.*;
 
 public class Consume8 {
   public static void main(String[] args) {
     Consumer<String> consumer = s -> System.out.println(s);
     consumer.accept("hello there");
     consumer.andThen(consumer).accept("this will be printed twice");
   }
 }
 

Predicate example

 import java.util.function.*;
 
 public class Predicate8 {
   public static void main(String[] args) {
     Predicate<Integer> gradeA = score -> score >= 90;
     System.out.println(gradeA.test(80));
     System.out.println(gradeA.test(90));
   }
 }
 

In addition to test method, you can also use and, negate, or method to combine other predicates.

Function example

 import java.util.function.*;
 
 public class Function8 {
   public static void main(String[] args) {
     BinaryOperator<Integer> adder = (n1, n2) -> n1 + n2;
     System.out.println("sum " + adder.apply(4, 5));
     Function<Double>,Double> square = x -> x * x;
     System.out.println("square " + square.apply(5.0));
   }
 }
 

Custom Functions

In addition to predefined functions, you can define your own interface for functions as long as there is a single method is declared. You can optionally declare interface with @FunctionalInterface annotation so that compiler can verify it, e.g.

 public class CustomFunction8 {
   @FunctionalInterface
   interface Command<T> {
     void execute(T obj);
   }
 
   private static <T> void invoke(Command<T> cmd, T arg) {
     cmd.execute(arg);
   }
 
 
   public static void main(String[] args) {
     Command<Integer> cmd = arg -> System.out.println(arg);
     invoke(cmd, 5);
   }
 }
 

Method Reference

In addition to passing lambda, you can also pass instance or static methods as closures using method reference. There are four kinds of method references:

  • Reference to a static method ContainingClass::staticMethodName
  • Reference to an instance method of a particular object ContainingObject::instanceMethodName
  • Reference to an instance method of an arbitrary object of a particular type ContainingType::methodName
  • Reference to a constructor ClassName::new

Streams

In addition to lambda support, Java 8 has updated collection classes to support streams. Streams don’t really store anything but they behave as pipes for computation lazily. Though, collections have limited size, but streams can be unlimited and they can be only consumed once. Streams can be accessed from collections using stream() and parallelStream() methods or from an array via Arrays.stream(Object[]). There are also static factory methods on the stream classes, such as Stream.of(Object[]), IntStream.range(int, int), etc. Common intermediate methods using as pipes that you can invoke on streams:

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

In above examples sorted, distinct, unsorted are stateful, whereas filter, map, limit are stateless. And here are terminal operations that trigger evaluation on streams:

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

You can implement your own streams by using helper methods in StreamSupport class.

Iterating

Java 8 streams support forEach method for iterating, which can optionally take a consumer function, e.g.

 import java.util.stream.Stream;
 
 public class StreamForEach {
   public static void main(String[] args) {
     Stream<String> symbols = Stream.of("AAPL", "MSFT", "ORCL", "NFLX", "TSLA");
     symbols.forEach(System.out::println);
   }
 }
 

Parallel iteration:

 import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Stream;
 
 public class ParStreamForEach {
   public static void main(String[] args) {
     List<String> symbols = Arrays.asList("AAPL", "MSFT", "ORCL", "NFLX", "TSLA");
     System.out.println("unordered");
     symbols.parallelStream().forEach(System.out::println);
     System.out.println("ordered");
     symbols.parallelStream().forEachOrdered(System.out::println);
   }
 }
 

Note that by default iterating parallel stream would be unordered but you can force ordered iteration using forEachOrdered method instead of forEach.

Filtering

We already saw predicate functions and filtering support in streams allow extract elements of collections that evaluates true to given predicate. Let’s create a couple of classes that we will use later:

 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.function.IntPredicate;
 
 
 public class Game implements IntPredicate {
   enum Type {
     AGE_ALL,
     AGE_13_OR_ABOVE,
     AGE_18_OR_ABOVE
   }
 
   public final String name;
   public final Type type;
   public final Collection<Player> players = new ArrayList<>();
 
 
   public Game(String name, Type type) {
     this.name = name;
     this.type = type;
   }
 
   public boolean suitableForAll() {
     return type == Type.AGE_ALL;
   }
   public void add(Player player) {
     if (test(player.age)) {
       this.players.add(player);
     }
   }
 
   @Override
   public boolean test(int age) {
     switch (type) {
       case AGE_18_OR_ABOVE:
         return age >= 18;
       case AGE_13_OR_ABOVE:
         return age >= 13;
       default:
         return true;
     }
   }
   @Override 
   public String toString() {
     return name;
   }
 }
 
 public class Player {
   public final String name;
   public final int age;
   public Player(String name, int age) {
     this.name = name;
     this.age = age;
   }
   @Override 
   public String toString() {
     return name;
   }
 }
 

Now let’s create a class that will filter games by types:

 import java.util.Arrays;
 import java.util.Collection;
 import static java.util.stream.Collectors.*;
 import java.util.stream.Stream;
 
 
 public class GameFilter {
   public static void main(String[] args) {
     Collection<Game>> games = Arrays.asList(new Game("Birdie", Game.Type.AGE_ALL), new Game("Draw", Game.Type.AGE_ALL), new Game("Poker", Game.Type.AGE_18_OR_ABOVE), new Game("Torpedo", Game.Type.AGE_13_OR_ABOVE));
     Collection<Game>> suitableForAll = games.stream().filter(Game::suitableForAll).collect(toList());
     System.out.println("suitable for all");
     suitableForAll.stream().forEach(System.out::println);
     Collection<Game>> adultOnly = games.stream().filter(game -> game.type == Game.Type.AGE_18_OR_ABOVE).limit(10).collect(toList());
     System.out.println("suitable for adults only");
     adultOnly.stream().forEach(System.out::println);
   }
 }
 

As you can see, filter can accept lambda or method reference.

Map

Map operation on streams applies a given function to transform each element in stream and produces another stream with transformed elements.

 import java.util.Arrays;
 import java.util.Collection;
 import static java.util.stream.Collectors.*;
 import java.util.stream.Stream;
 
 
 public class GameMap {
   public static void main(String[] args) {
     Collection<Game>> games = Arrays.asList(new Game("Birdie", Game.Type.AGE_ALL), new Game("Draw", Game.Type.AGE_ALL), new Game("Poker", Game.Type.AGE_18_OR_ABOVE), new Game("Torpedo", Game.Type.AGE_13_OR_ABOVE));
     Collection<Player> players = Arrays.asList(new Player("John", 10), new Player("David", 15), new Player("Matt", 20), new Player("Dan", 30), new Player("Erica", 5));
     for (Game game : games) {
       for (Player player : players) {
         game.add(player);
       }
     }
     //
     Collection<Game>.Type> types = games.stream().map(game -> game.type).collect(toList());
     System.out.println("types:");
     types.stream().forEach(System.out::println);
     Collection<Player> allPlayers = games.stream().flatMap(game -> game.players.stream()).collect(toList());
     System.out.println("\nplayers:");
     players.stream().forEach(System.out::println);
   }
 }
 
 

Note that flatMap takes collection of objects for each input and flattens it and produces a single collection. Java streams also produces map methods for primitive types such as mapToLong, mapToDouble, etc.

Sorting

Previously, you had to implement Comparable interface or provide Comparator for sorting but you can now pass lambda for comparison, e.g.

 import java.util.Arrays;
 import java.util.List;
 import static java.util.stream.Collectors.*;
 import java.util.stream.Stream;
 import java.util.Comparator;
 
 
 public class GameSort {
   public static void main(String[] args) {
     List<Player> players = Arrays.asList(new Player("John", 10), new Player("David", 15), new Player("Matt", 20), new Player("Dan", 30), new Player("Erica", 5));
     players.sort(Comparator.comparing(player -> player.age));
     System.out.println(players);
   }
 }
 

Min/Max

Java streams provide helper methods for calculating min/max, e.g.

 import java.util.Arrays;
 import java.util.Collection;
 import static java.util.stream.Collectors.*;
 import java.util.stream.Stream;
 import java.util.Comparator;
 
 
 public class GameMinMax {
   public static void main(String[] args) {
     Collection<Player> players = Arrays.asList(new Player("John", 10), new Player("David", 15), new Player("Matt", 20), new Player("Dan", 30), new Player("Erica", 5));
     Player min = players.stream().min(Comparator.comparing(player -> player.age)).get();
     Player max = players.stream().max(Comparator.comparing(player -> player.age)).get();
     System.out.println("min " + min + ", max " + max);
   }
 }
 
 

Reduce/Fold

Reduce or fold generalizes the problem where we compuate a single value from collection, e.g.

 import java.util.Arrays;
 import java.util.Collection;
 import static java.util.stream.Collectors.*;
 import java.util.stream.Stream;
 
 
 public class GameReduce {
   public static void main(String[] args) {
     Collection<Player> players = Arrays.asList(new Player("John", 10), new Player("David", 15), new Player("Matt", 20), new Player("Dan", 30), new Player("Erica", 5));
     double averageAge1 = players.stream().mapToInt(player -> player.age).average().getAsDouble();
     double averageAge2 = players.stream().mapToInt(player -> player.age).reduce(0, Integer::sum) / players.size();
     double averageAge3 = players.stream().mapToInt(player -> player.age).reduce(0, (sum, age) -> sum + age) / players.size();
     System.out.println("average age " + averageAge1 + ", " + averageAge2 + ", or " + averageAge3);
   }
 }
 

Grouping/Partitioning

groupingBy method of Collectors allows grouping collection, e.g.

 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import static java.util.stream.Collectors.*;
 import java.util.stream.Stream;
 
 
 public class GameGrouping {
   public static void main(String[] args) {
     Collection<Player> players = Arrays.asList(new Player("John", 10), new Player("David", 15), new Player("Matt", 20), new Player("Dan", 30), new Player("Erica", 5));
     Map<Integer, List<Player>> playersByAge = players.stream().collect(groupingBy(player -> player.age));
     System.out.println(playersByAge);
   }
 }
 

partitioningBy groups collection into two collection, e.g.

 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import static java.util.stream.Collectors.*;
 import java.util.stream.Stream;
 
 
 public class GamePartition {
   public static void main(String[] args) {
     Collection<Player> players = Arrays.asList(new Player("John", 10), new Player("David", 15), new Player("Matt", 20), new Player("Dan", 30), new Player("Erica", 5));
     Map<Boolean, List<Player>> playersByAge = players.stream().collect(groupingBy(player -> player.age >= 18));
     System.out.println(playersByAge);
   }
 }
 

String joining

Here is an example of creating a string from collection:

 import static java.util.stream.Collectors.*;
 import java.util.stream.Stream;
 
 
 public class GameJoin {
   public static void main(String[] args) {
     Stream<Player> players = Stream.of(new Player("John", 10), new Player("David", 15), new Player("Matt", 20), new Player("Dan", 30), new Player("Erica", 5));
     System.out.println(players.map(Player::toString).collect(joining(",", "[", "]")));
   }
 }
 

Lazy Evaluation

Java Map interface now supports lazy evaluation by adding object to Map if the key is not present:

 import java.util.HashMap;
 import java.util.Map;
 
 public class Fib {
   private final static Map<Integer,Long> cache = new HashMap<Integer, Long>() {{
     put(0,0L);
     put(1,1L);
   }};
   public static long fib(int x) {
     return cache.computeIfAbsent(x, n -> fib(n-1) + fib(n-2));
   }
 
   public static void main(String[] args) {
     System.out.println(fib(10));
   }
 }
 

Parallel Streams

Though, streams processes elements serially but you can change stream() method of collection to parallelStream to take advantage of parallel processing of stream. Parallel stream use ForkJoinPool by default and use as many threads as you have processors (Runtime.getRuntime().availableProcessors()), e.g.

 import java.util.Arrays;
 import java.util.List;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
 
 public class ParStreamPrime {
   private static boolean isPrime(int n) {
     if (n%2==0) return false;
     for(int i=3;i*i<=n;i+=2) {
       if(n%i==0) return false;
     }
     return true;
   }
   private static long serialTest(int max) {
     long started = System.currentTimeMillis();
     IntStream.rangeClosed(1, max).forEach(num -> isPrime(num));
     return System.currentTimeMillis() - started;
   }
   private static long parallelTest(int max) {
     long started = System.currentTimeMillis();
     IntStream.rangeClosed(1, max).parallel().forEach(num -> isPrime(num));
     return System.currentTimeMillis() - started;
   }
   //
   public static void main(String[] args) {
     int max = 1000000;
     System.out.println("Serial " + serialTest(max));
     System.out.println("Parallel " + parallelTest(max));
   }
 }
 

If you need to customize thread pool size, you can create parallel stream inside fork-join-pool, e.g.

   private static void parallelTest(final int max) {
     ForkJoinPool forkJoinPool = new ForkJoinPool(2);
     forkJoinPool.submit(() ->
        IntStream.rangeClosed(1, max).parallel().forEach(num -> isPrime(num));
     ).get();
   }
 

Default methods and Mixins

For anyone who has to support interfaces for multiple clients knows the frustration of adding new methods because it requires updating all clients. You can now add default methods on interfaces and add static methods, e.g.

 interface Vehicle {
   float getMaxSpeedMPH();
   public static String getType(Vehicle v) {
     return v.getClass().getSimpleName();
   }
 }
 
 interface Car extends Vehicle {
   void drive();
   public default float getMaxSpeedMPH() {
     return 200;
   }
 }
 interface Boat extends Vehicle {
   void row();
   public default float getMaxSpeedMPH() {
     return 100;
   }
 }
 
 interface Plane extends Vehicle {
   void fly();
   public default float getMaxSpeedMPH() {
     return 500;
   }
 }
 
 public class AmphiFlyCar implements Car, Boat, Plane {
   @Override
   public void drive() {
     System.out.println("drive");
   }
   @Override
   public void row() {
     System.out.println("row");
   }
   @Override
   public void fly() {
     System.out.println("fly");
   }
   public float getMaxSpeedMPH() {
     return Plane.super.getMaxSpeedMPH();
   }
   public static void main(String[] args) {
     AmphiFlyCar v = new AmphiFlyCar();
     System.out.println(Vehicle.getType(v) + ": " + v.getMaxSpeedMPH());
   }
 }
 

Optional

One of biggest bane of Java applications is NullPointerException and you can get rid of those using Optional, which acts as Maybe monads in other languages, e.g.

 import java.util.HashMap;
 import java.util.Map;
 import static java.util.stream.Collectors.*;
 import java.util.stream.Stream;
 import java.util.Optional;
 
 
 public class OptionalExample {
   private static Map<String, Player> players = new HashMap<String, Player>() {{
     put("John", new Player("John", 10));
     put("David", new Player("David", 15));
     put("Matt", new Player("Matt", 20));
     put("Erica", new Player("Erica", 25));
   }};
 
   private static Optional<Player> findPlayerByName(String name) {
     Player player = players.get(name);
     return player == null ? Optional.empty() : Optional.of(player);
   }
 
   private static Integer getAge(Player player) {
     return player.age;
   }
 
 
   public static void main(String[] args) {
     findPlayerByName("John").ifPresent(System.out::println);
     Player player = findPlayerByName("Jeff").orElse(new Player("Jeff", 40));
     System.out.println("orElse " + player);
     Integer age = findPlayerByName("Jeff").map(OptionalExample::getAge).orElse(-1);
     System.out.println("Jeff age " + age);
   }
 }
 

CompletableFuture

Java has long supported future, but previously you had to call blocking get() to retrieve the result. With Java 8, you can use CompletableFuture to define the behavior when asynchronous processing is completed, e.g.

 private static final ExecutorService executor = Executors.newFixedThreadPool(2);
 public static CompletableFuture getPlayer(String name) {
    return CompletableFuture.supplyAsync(() -> new Player(), executor);
 }
 getQuote("name").thenAccept(player -> System.out.println(player));
 
 

Summary

Over the past few years, functional programming languages have become mainstream and Java 8 brings many of those capabilites despite being late. These new features help write better and more concise code. You will have to change existing code to make more use of immutable objects and use streams instead of objects when possible. As far as stability, I found java 8 compiler on Linux environment a bit buggy that crashed often so it may take a little while before Java 8 can be used in production.


Powered by WordPress