Computer Science 312 What’s New in Java 8? 1
What Is Pipelining? Unix (or Linux) allows you to “pipe” data through a series of transformations using the | operator Transformations: Glue two files together Convert the result to lowercase Sort the resulting lines Return the last three of those lines cat file1 file2 | tr "[A-Z]" "[a-z]" | sort | tail -3
21st Century Computing Enormous amounts of data to be processed (“Big Data”) Many CPUs are available, either via multicore or on a network Would like a way of harnessing multiple processors to massage these data in a pipelined fashion, while using a simple, declarative style in our code
21st Century Computing In functional languages, data can be pipelined through a series of transformations, using a high- level, declarative style Essential elements: Map Filter Reduce (fold) Lambda expressions
21st Century Computing Because functional languages forbid side effects, the work of these transformations can be dispatched to multiple processors Essential elements: Map Filter Reduce (fold) Lambda expressions
What about the Rest of Us? In most conventional languages, massaging data involves iterating through various types of collections to modify their elements; the style of this is anything but declarative Although object orientation mitigates the potential for harmful side effects, programs in most conventional languages suffer the pitfalls of managing shared memory when resorting to concurrency
Java 8 to the Rescue! Java 8, released in 2014, includes new features that allow programs to process data in a pipelined manner, across multiple CPUs, with a declarative style of code The new features are Streams (including parallel streams) Methods (including lambdas) as arguments Higher-order functions for mapping and filtering Higher-order functions for collecting results
Sorting the Old-Fashioned Way // java.util.Comparator public interface Comparator<T> { public int compare(T o1, T o2); } // java.util.Collections.sort static <T> void sort(List<T> list, Comparator<? super T> c) Collections.sort supports the strategy pattern Define a class that implements Comparator and pass an instance of this class to sort, for a particular type of comparison
Sorting the Old-Fashioned Way // java.util.Comparator public interface Comparator<T> { public int compare(T o1, T o2); } // java.util.Collections.sort static <T> void sort(List<T> list, Comparator<? super T> c) Collections.sort(inventory, new Comparator<Apple>() { public int compare(Apple a1, Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } }); An instance of the strategy pattern Clunky: must create an instance of an anonymous class
Generalized Sorting with a lambda // java.util.Comparator public interface Comparator<T> { public int compare(T o1, T o2); } // java.util.Collections.sort static <T> void sort(List<T> list, Comparator<? super T> c) inventory.sort( (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())); Still a strategy, but we now pass an explicit behavior, without the extra fluff of a class definition Much more declarative in style!
Filtering the Old-Fashioned Way
Filtering the Java 8 Way
Pass Method References filterApples(inventory, Apple::isGreenApple); filterApples(inventory, Apple::isHeavyApple);
Or Pass Lambda Expressions filterApples(inventory, (Apple a) -> "green".equals(a.getColor()) ); filterApples(inventory, (Apple a) -> a.getWeight() > 150 );
Generalize to a filter Method static <T> Collection<T> filter(Collection<T> c, Predicate<T> p){ // Build and return a collection of filtered results } filter(inventory, (Apple a) -> "green".equals(a.getColor()) ); filter(inventory, (Apple a) -> a.getWeight() > 150 ); Could do this, but we won’t Instead, we’ll create a stream from the collection, use the filter method in the java.util.stream API on this stream, and collect the results
Stream, Filter, and Collect import static java.util.stream.Collectors.toList; List<Apple> greenApples = inventory.stream().filter((Apple a) -> "green".equals(a.getColor()) .collect(toList()); List<Apple> heavyApples = inventory.stream().filter((Apple a) -> a.getWeight() > 150) Instead, we’ll create a stream from the collection use the filter method in the java.util.stream API on this stream collect the results
Filter and Group Transactions Lots of control-flow (for and if statements)
Filter and Group Transactions Drop control, use declarative style!
To Stream or to Parallel Stream? Sequential stream: import static java.util.stream.Collectors.toList; List<Apple> heavyApples = inventory.stream().filter((Apple a) -> a.getWeight() > 150) .collect(toList());
To Stream or to Parallel Stream? Sequential stream: import static java.util.stream.Collectors.toList; List<Apple> heavyApples = inventory.stream().filter((Apple a) -> a.getWeight() > 150) .collect(toList()); Parallel stream: import static java.util.stream.Collectors.toList; List<Apple> heavyApples = inventory.parallelStream().filter((Apple a) -> a.getWeight() > 150) .collect(toList());
Other Cool Stuff in Java 8 Default methods – can include method implementations in an interface, so implementers of its classes won’t go crazy when new methods are added Optional types – no more null pointer exceptions, due to having to return either null or some full- fledged object; specifies the Optional<T> type with accessor methods
Lambda expressions in Java Chapter 3 For next time Lambda expressions in Java Chapter 3