Lambdas & Streams In JDK8 Making Bulk Opera/ons Simple
Simon Ri6er Head of Java Technology Evangelism Oracle Corp. Twi6er: @speakjava
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Safe Harbor Statement The following is intended to outline our general product direcTon. It is intended for informaTon purposes only, and may not be incorporated into any contract. It is not a commitment to deliver any material, code, or funcTonality, and should not be relied upon in making purchasing decisions. The development, release, and Tming of any features or funcTonality described for Oracle’s products remains at the sole discreTon of Oracle.
3
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
1996 … 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 1.0 5.0 6 7 8 java.lang.Thread
java.util.concurrent (jsr166)
Fork/Join Framework (jsr166y)
Project Lambda Concurrency in Java
Phasers, etc (jsr166)
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
The Problem: External IteraTon
List<Student> students = ... double highestScore = 0.0; for (Student s : students) { if (s.getGradYear() == 2011) { if (s.getScore() > highestScore) highestScore = s.score; } }
• Our code controls iteraTon • Inherently serial: iterate from
beginning to end • Not thread-‐safe
• Business logic is stateful • Mutable accumulator variable
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Internal IteraTon With Inner Classes
• IteraTon handled by the library • Not inherently serial – traversal may be done in parallel
• Traversal may be done lazily – so one pass, rather than three
• Thread safe – client logic is stateless • High barrier to use
– SyntacTcally ugly
More FuncTonal double highestScore = students .filter(new Predicate<Student>() { public boolean op(Student s) { return s.getGradYear() == 2011; } }) .map(new Mapper<Student,Double>() { public Double extract(Student s) { return s.getScore(); } }) .max();
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Internal IteraTon With Lambdas List<Student> students = ... double highestScore = students .filter(Student s -‐> s.getGradYear() == 2011) .map(Student s -‐> s.getScore()) .max();
• More readable • More abstract
• Less error-‐prone
NOTE: This is not JDK8 code
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Lambda Expressions
• Lambda expressions represent anonymous funcTons – Same structure as a method
• typed argument list, return type, set of thrown excepTons, and a body
– Not associated with a class • We now have parameterised behaviour, not just values
Some Details
double highestScore = students. filter(Student s -‐> s.getGradYear() == 2011). map(Student s -‐> s.getScore()) max();
What
How
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Lambda Expression Types
• Single-‐method interfaces are used extensively in Java – DefiniTon: a func2onal interface is an interface with one abstract method – Func2onal interfaces are idenTfied structurally – The type of a lambda expression will be a func2onal interface
• Lambda expressions provide implementaTons of the abstract method
interface Comparator<T> { boolean compare(T x, T y); } interface FileFilter { boolean accept(File x); } interface Runnable { void run(); } interface ActionListener { void actionPerformed(…); } interface Callable<T> { T call(); }
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Local Variable Capture
• Lambda expressions can refer to effec2vely final local variables from the enclosing scope
• EffecTvely final: A variable that meets the requirements for final variables (i.e., assigned once), even if not explicitly declared final
• Closures on values, not variables
void expire(File root, long before) { root.listFiles(File p -‐> p.lastModified() <= before);
}
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
What Does ‘this’ Mean For Lambdas?
• ‘this’ refers to the enclosing object, not the lambda itself • Think of ‘this’ as a final predefined local • Remember the Lambda is an anonymous func2on
– It is not associated with a class – Therefore there can be no ‘this’ for the Lambda
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Referencing Instance Variables Which are not final, or effecTvely final class DataProcessor { private int currentValue; public void process() { DataSet myData = myFactory.getDataSet(); dataSet.forEach(d -‐> d.use(currentValue++)); } }
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Referencing Instance Variables The compiler helps us out class DataProcessor { private int currentValue; public void process() { DataSet myData = myFactory.getDataSet(); dataSet.forEach(d -‐> d.use(this.currentValue++); } }
‘this’ (which is effecTvely final) inserted by the compiler
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Type Inference
• The compiler can oien infer parameter types in a lambda expression § Inferrence based on the target funcTonal interface’s method signature
• Fully staTcally typed (no dynamic typing sneaking in) – More typing with less typing
List<String> list = getList(); Collections.sort(list, (String x, String y) -‐> x.length() -‐ y.length());
Collections.sort(list, (x, y) -‐> x.length() -‐ y.length());
static T void sort(List<T> l, Comparator<? super T> c);
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Method References
• Method references let us reuse a method as a lambda expression
FileFilter x = File f -‐> f.canRead();
FileFilter x = File::canRead;
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Constructor References
• Same concept as a method reference – For the constructor
Factory<List<String>> f = ArrayList<String>::new;
Factory<List<String>> f = () -‐> return new ArrayList<String>();
Replace with
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Library EvoluTon Goal
• Requirement: aggregate operaTons on collecTons – New methods required on CollecTons to facilitate this
• This is problemaTc – Can’t add new methods to interfaces without modifying all implementaTons – Can’t necessarily find or control all implementaTons
int heaviestBlueBlock = blocks .filter(b -‐> b.getColor() == BLUE) .map(Block::getWeight) .reduce(0, Integer::max);
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
SoluTon: Default Methods
• Specified in the interface • From the caller’s perspecTve, just an ordinary interface method • Provides a default implementaTon
• Default only used when implementaTon classes do not provide a body for the extension method
• ImplementaTon classes can provide a be6er version, or not
interface Collection<E> { default Stream<E> stream() { return StreamSupport.stream(spliterator()); } }
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Virtual Extension Methods
• Err, isn’t this implemenTng mulTple inheritance for Java? • Yes, but Java already has mulTple inheritance of types • This adds mulTple inheritance of behavior too • But not state, which is where most of the trouble is • Can sTll be a source of complexity
• Class implements two interfaces, both of which have default methods • Same signature • How does the compiler differenTate?
• StaTc methods also allowed in interfaces in Java SE 8
Stop right there!
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
FuncTonal Interface DefiniTon
• Single Abstract Method (SAM) type • A funcTonal interface is an interface that has one abstract method
– Represents a single funcTon contract – Doesn’t mean it only has one method
• @FunctionalInterface annotaTon – Helps ensure the funcTonal interface contract is honoured – Compiler error if not a SAM
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Aggregate OperaTons
• Most business logic is about aggregate operaTons – “Most profitable product by region” – “Group transacTons by currency”
• As we have seen, up to now, Java uses external iteraTon – Inherently serial – FrustraTngly imperaTve
• Java SE 8’s answer: The Stream API – With help from Lambdas
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Stream Overview
• AbstracTon for specifying aggregate computaTons – Not a data structure – Can be infinite
• Simplifies the descripTon of aggregate computaTons – Exposes opportuniTes for opTmisaTon – Fusing, laziness and parallelism
At The High Level
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Stream Overview
• A stream pipeline consists of three types of things – A source – Zero or more intermediate operaTons – A terminal operaTon
• Producing a result or a side-‐effect
Pipeline
int total = transactions.stream() .filter(t -‐> t.getBuyer().getCity().equals(“London”)) .mapToInt(Transaction::getPrice) .sum();
Source
Intermediate operaTon Terminal operaTon
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Stream Sources
• From collecTons and arrays – Collection.stream() – Collection.parallelStream() – Arrays.stream(T array) or Stream.of()
• StaTc factories – IntStream.range() – Files.walk()
• Roll your own – java.util.Spliterator
Many Ways To Create
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Stream Sources Provide
• Access to stream elements • DecomposiTon (for parallel operaTons)
– Fork-‐join framework
• Stream characterisTcs – ORDERED – SORTED – DISTINCT – SIZED – NONNULL – IMMUTABLE – CONCURRENT
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Stream Terminal OperaTons
• The pipeline is only evaluated when the terminal operaTon is called – All operaTons can execute sequenTally or in parallel – Intermediate operaTons can be merged
• Avoiding mulTple redundant passes on data • Short-‐circuit operaTons (e.g. findFirst) • Lazy evaluaTon
– Stream characterisTcs help idenTfy opTmisaTons • DISTINT stream passed to distinct() is a no-‐op
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Maps and FlatMaps Map Values in a Stream
Map
FlatMap
Input Stream
Input Stream
1-‐to-‐1 mapping
1-‐to-‐many mapping
Output Stream
Output Stream
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Optional<T> Reducing NullPointerException Occurrences String direction = gpsData.getPosition().getLatitude().getDirection(); String direction = “UNKNOWN”; if (gpsData != null) { Position p = gpsData.getPosition(); if (p != null) { Latitude latitude = p.getLatitude(); if (latitude != null) direction = latitude.getDirection(); } }
String direction = gpsData.getPosition().getLatitude().getDirection();
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Optional<T>
• Indicates that reference may, or may not have a value – Makes developer responsible for checking – A bit like a stream that can only have zero or one elements
Reducing NullPointerException Occurrences
Optional<GPSData> maybeGPS = Optional.ofNullable(gpsData); maybeGPS.ifPresent(GPSData::printPosition); GPSData gps = maybeGPS.orElse(new GPSData()); maybeGPS.filter(g -‐> g.lastRead() < 2).ifPresent(GPSData.display());
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Example 1 Convert words in list to upper case
List<String> output = wordList .stream() .map(String::toUpperCase) .collect(Collectors.toList());
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Example 1 Convert words in list to upper case (in parallel)
List<String> output = wordList .parallelStream() .map(String::toUpperCase) .collect(Collectors.toList());
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Example 2
• BufferedReader has new method – Stream<String> lines()
Count lines in a file
long count = bufferedReader .lines() .count();
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Example 3 Join lines 3-‐4 into a single string
String output = bufferedReader .lines() .skip(2) .limit(2) .collect(Collectors.joining());
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Example 4 Collect all words in a file into a list
List<String> output = reader .lines() .flatMap(line -‐> Stream.of(line.split(REGEXP))) .filter(word -‐> word.length() > 0) .collect(Collectors.toList());
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Example 5 List of unique words in lowercase, sorted by length
List<String> output = reader .lines() .flatMap(line -‐> Stream.of(line.split(REGEXP))) .filter(word -‐> word.length() > 0) .map(String::toLowerCase) .distinct() .sorted((x, y) -‐> x.length() -‐ y.length()) .collect(Collectors.toList());
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Example 6: Real World Infinite stream from thermal sensor
private int double currentTemperature; ... thermalReader .lines() .mapToDouble(s -‐> Double.parseDouble(s.substring(0, s.length() -‐ 1))) .map(t -‐> ((t – 32) * 5 / 9) .filter(t -‐> t != currentTemperature) .peek(t -‐> listener.ifPresent(l -‐> l.temperatureChanged(t))) .forEach(t -‐> currentTemperature = t);
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Example 6: Real World Infinite stream from thermal sensor
private int double currentTemperature; ... thermalReader .lines() .mapToDouble(s -‐> Double.parseDouble(s.substring(0, s.length() -‐ ))) .map(t -‐> ((t – 32) * 5 / 9) .filter(t -‐> t != this.currentTemperature) .peek(t -‐> listener.ifPresent(l -‐> l.temperatureChanged(t))) .forEach(t -‐> this.currentTemperature = t);
Copyright © 2014, Oracle and/or its affiliates. All rights reserved.
Conclusions
• Java needs lambda statements – Significant improvements in exisTng libraries are required
• Require a mechanism for interface evoluTon – SoluTon: virtual extension methods
• Bulk operaTons on CollecTons – Much simpler with Lambdas
• Java SE 8 evolves the language, libraries, and VM together