JAVA 8 - BY EXAMPLE!PATTERNS AND PRACTICES FOR LAMBDAS, STREAMS,
OPTIONAL...Created by / Mark Harrison @markglh
INTERFACE DEFAULT METHODSInterfaces can now have default & static methods!Allows new interface methods to be added without breakingexisting implementationsMultiple inheritance!
public interface Comparator<T> { default Comparator<T> reversed() { // Implementation here } public static Comparator<T> naturalOrder() { // Implementation here } }
MULTIPLE INHERITANCE...What if a class inherits the same default method from twointerfaces???
1. Class methods always win. Whether it’s this class or thesuperclass, it will take priority
2. If multiple interfaces de�ne the same method with adefault implementation, then the most speci�c is selected(the child in inheritance terms)
3. If it’s not obvious then the compiler will cry, we need toexplicitly choose: INTERFACE.super.METHODNAME
LAMBDAS YOU SAY?Basically just a shorthand method implementationConcise and much improved replacement for Anonymousinner classesMakes it easy to pass behaviour aroundPass the behaviour into the method, �ips the design on itsheadLexically scoped (this is effectively shared with thesurrounding method)
SYNTAX PLEASE!(int x, int y) -> x + y
Argument types can be inferred by the compiler:
(x, y) -> x + y
Zero-arg Lambdas are also possible:
() -> "Java 8!"
But how can we use them?
Consumer<String> inferredConsumer = x -> System.out.println(x);
WAT?!?
FUNCTIONAL INTERFACESA Functional Interface is any interface with one SingleAbstract MethodThe parameters that the Lambda accepts and returns mustmatch the interface (including exceptions)@FunctionalInterface annotation is provided tohighlight these interfaces, it is just a marker though, any SAMinterface will work - the annotation simply enforces this atcompile time
So... When we pass or assign a Lambda:1. First the Lambda is converted into a Functional Interface2. Secondly it is invoked via this generated implementation
LAMBDA EXAMPLE 1 - PREDICATESHave you ever written code like this?
@FunctionalInterface //Added in Java8 public interface Predicate<T> { boolean test(T t); } private void printMatchingPeople( List<Person> people, Predicate<Person> predicate) { for (Person person : people) { if (predicate.test(person)) { System.out.println(person); } } }
LAMBDA EXAMPLE 1 - PREDICATESPre Java 8:
public class PersonOver50Predicate implements Predicate<Person> { public boolean test(Person person) { return person.getAge() > 50; } } printMatchingPeople(loadsOfPeople, new PersonOver50Predicate());
LAMBDA EXAMPLE 1 - PREDICATESJava 8:
printMatchingPeople(loadsOfPeople, x -> x.getAge() > 50);
Notice the signature matches that of the Predicate, the compilerautomatically �gures out the rest
What if we had an existing Predicate we wanted to enhance?
Composite Predicates FTW... :-)
Predicate<Person> ageCheck = x -> x.getAge() > 50; printMatchingPeople(loadsOfPeople, ageCheck.and(x -> x.getIq() > 100));
LAMBDA EXAMPLE 2 - RUNNABLEPre Java 8 mess:
Runnable r1 = new Runnable() { @Override public void run() { System.out.println("Meh!"); } }; r1.run();
Java 8:
Runnable r = () -> System.out.println("Woop!"); r.run();
LAMBDA EXAMPLE 3 - COLLECTIONSLambdas make lots of new Collection methods possible...
List<String> strings = new ArrayList<>(); Collections.addAll(strings, "Java", "7", "FTW");
Do something on every element in the List
strings.forEach(x -> System.out.print(x + " ")); //Prints: "Java 7 FTW"
LAMBDA EXAMPLE 4 - MORE COLLECTIONSReplace every matching element in the List
strings.replaceAll(x -> x == "7" ? "8" : x); strings.forEach(x -> System.out.print(x + " ")); //Prints: "Java 8 FTW"
Remove matching elements from the List
strings.removeIf(x -> x == "8"); strings.forEach(x -> System.out.print(x + " ")); //Prints: Java FTW
LAMBDA EXAMPLE 5 - COMPARATORS@FunctionalInterface //Added in Java8 version public interface Comparator<T> { int compare(T o1, T o2); //Java 8 adds loads of default methods }
LAMBDA EXAMPLE 6 - COMPARATORS 2List<Person> loadsOfPeople = ...;
Pre Java 8:
public class SortByPersonAge implements Comparator<Person> { public int compare(Person p1, Person p2) { return p1.getAge() - p2.getAge(); } } Collections.sort(loadsOfPeople, new SortByPersonAge());
Java 8:
Collections.sort(loadsOfPeople, (p1, p2) -> p1.getAge() - p2.getAge());
LAMBDA EXAMPLE 7 - COMPARATORS 3As usual in Java 8... the Comparator interface provides plenty
of useful default & static methods...
//"comparing" static method simplifies creation Comparator<Person> newComparator = Comparator.comparing(e -> e.getIq());
//"thenComparing" combines comparators Collections.sort(loadsOfPeople, newComparator.thenComparing( Comparator.comparing(e -> e.getAge())));
LAMBDA EXAMPLE 8 - COMPARATORS 4and more...
//More useful Collection methods... loadsOfPeople4.sort( Comparator.comparing(e -> e.getIq()));
//And finally... Method references loadsOfPeople.sort( Comparator.comparing(Person::getAge));
INTRODUCING METHOD REFERENCESAny method can be automatically “lifted” into a function. Itmust simply meet contract of the FunctionalInterfaceCan be easier to debug & test than Lambdas, more descriptivestack tracesPromotes re-use, keeping code DRYUses the "::" syntax
METHOD REFERENCES TYPESReference to... Example
a static method Class::staticMethodName
an instance method ofa speci�c object
object::instanceMethodName
an instance method ofan arbitrary object
Class::methodName
a constructor ClassName::new
REFERENCE TO A STATIC METHODA simple reference to a static method
//isPersonOver50 is a static method printMatchingPeople(loadsOfPeople, PersonPredicates::isPersonOver50);
This is equivalent to
printMatchingPeople(loadsOfPeople, x -> x.getAge() > 50);
REFERENCE TO AN INSTANCE METHOD OF ASPECIFIC OBJECT
A reference to a method on an object instance
List<String> strings = ... //print is a method on the "out" PrintStream object strings.forEach(System.out::print);
This is equivalent to
strings.forEach(x -> System.out.print(x));
REFERENCE TO AN INSTANCE METHOD OF ANARBITRARY OBJECT
Examine this simpli�ed de�nition of a map function
public interface Function<T,R> { public R apply(T t); } public <T, R> List<R> map(Function<T, R> function, List<T> source) { /* applies the function to each element, converting it from T to R */ }
REFERENCE TO AN INSTANCE METHOD OF ANARBITRARY OBJECT CONT...
Although it looks like we're referencing a Class method, we'reinvoking an instance method on the object(s) passed in the call
List<Person> loadsOfPeople = ... List<Integer> namesOfPeople = map(Person::getAge, loadsOfPeople);
This is equivalent to
map(person -> person.getAge(), loadsOfPeople);
REFERENCE TO A CONSTRUCTORUses the constructor to create new objects, the constructor
signature must match that of the @FunctionalInterface
List<String> digits = Arrays.asList("1", "2", "3");
//Transforms a String into a new Integer List<Integer> numbers = map(Integer::new, digits);
This is equivalent to
map(s -> new Integer(s), digits);
WHAT'S WRONG WITH COLLECTIONSEvery application uses Collections, however Collections aredif�cult to query and aggregate, requiring several levels ofiteration and conditionals, basically it’s messy and painfulWriting multi-threaded code to do this manually is dif�cult towrite and maintain
Imagine writing this manually
Stream<String> words=Stream.of("Java", "8", "FTW"); Map<String, Long> letterToNumberOfOccurrences = words.map(w -> w.split("")) .flatMap(Arrays::stream) .collect(Collectors.groupingBy( Function.identity(), Collectors.counting())); //Prints: //{a=2, T=1, F=1, v=1, W=1, 8=1, J=1}
INTRODUCING STREAMS!A Stream is a conceptually �xed data structure in whichelements are computed on demandStreams iterate internally, you don’t have to worry abouthandling the iterationPipelining: Akin to the “pipe” command in unix, allowingaggregations to be chained togetherAutomatic optimisation: short-circuiting and lazy processingCan be in�niteCan be executed in parallel automatically usingparallelStream or parallel()
MORE STREAMS...Can be created from multiple sources:
Arrays.stream(...), Stream.of(1, 2, 3, 4),Stream.iterate(...), Stream.range(...),Random.ints(), Files.lines(...)...
Two types of operations:
Intermediate (aggregation): �lter, map, �atMap, sorted ...Terminal: collect, reduce, forEach, �ndAny ...
Specialised Streams:
IntStream, LongStream and DoubleStream: betterperformance for those unboxed types
MAPpublic <R> Stream<R> map(Function<T, R> mapper);
The mapper Function converts each element from T to R. Theresult is then added, as is, to the Stream
FLATMAPpublic <R> Stream<R> flatMap(Function<T, Stream<R>> mapper);
The mapper Function converts each element from T to aStream of RThis is the key difference to map, the function itself returns aStream rather than one elementThis Stream is then �attened (merged) into the main Stream
To put it another way: �atMap lets you replace each value of aStream with another Stream, and then it concatenates all the
generated streams into one single stream
REDUCEpublic T reduce(T identity, BinaryOperator<T> accumulator); public Optional<T> reduce( BinaryOperator<T> accumulator); //This is the function contained in BinaryOperator R apply(T t, U u);
Terminal OperationTakes a Stream of values and repeatedly applies theaccumulator to reduce them into a single valueThe accumulator is passed the total so far (T) and the currentelement (U)If passed, identity provides the initial value, rather than the�rst element
REDUCE CONTINUED...int totalAgeUsingReduce = loadsOfPeople.stream() .map(Person::getAge) //contains 5, 10, 15 .reduce(0, (total, current) -> total + current);
1. First we map the Person to the age int2. reduce then starts at 0, and adds the current element, 53. reduce continues by adding the current total 5, to the next
element, 104. �nally reduce adds the current total 15, to the next element15
5. Tada, we've added up all the ages: 30!
COLLECTORSThe collect method is a terminal operation which takes various
"recipes", called Collectors for accumulating the elements ofa stream into a summary result, or converting to a speci�c type
(such as a List)
List<String> listOfStrings = loadsOfPeople.stream() .map(x -> x.getName()) .collect(Collectors.toList());
The argument passed to collect is an object of type java.util.stream.CollectorIt describes how to accumulate the elements of a stream intoa �nal resultCan be used for Grouping, Partitioning, Averaging, ...
STREAMS EXAMPLE 1List<Integer> numbers = Arrays.asList(1, 2 ... 8); List<Integer> twoEvenSquares = numbers.stream() .filter(n -> n % 2 == 0) //Filter odd numbers .map(n -> n * n) ////Multiply by itself .limit(2)//Limit to two results .collect(Collectors.toList()); //Finish!
Imagine a println in each step...
filtering 1
filtering 2
mapping 2
filtering 3
filtering 4
mapping 4
twoEvenSquares = List[4, 16]
STREAMS EXAMPLE 2List<String> youngerPeopleSortedByIq = loadsOfPeople.stream() .filter(x -> x.getAge() < 50) .sorted(Comparator .comparing(Person::getIq).reversed()) .map(Person::getName) .collect(Collectors.toList());
1. Filter out all people older than 502. Inverse sort the remaining people by IQ3. map each person to their name (convert to a Stream<String>)4. Convert the result to a List
STREAMS EXAMPLE 3 - SUMint combinedAge = loadsOfPeople.stream() .mapToInt(Person::getAge) //returns IntStream .sum(); //this HAS to be a specialised Stream
1. map each person to their age, producing an IntStream2. sum the results, also supports average
STREAMS EXAMPLE 4 - MAP, REDUCEString xml = "<people>" + loadsOfPeople.stream() .map(x -> "<person>"+ x.getName() +"</person>") .reduce("", String::concat) //start with "" + "</people>";
map each Person to an XML element(<person>Steve</person>), then use String.concat to reduce
the Stream into one XML String
<people> <person>Dave</person> <person>Helen</person> <person>Laura</person> <person>Ben</person> </people>
STREAMS EXAMPLE 5 - MAPList<Stream<Person>> clonedPeople = loadsOfPeople.stream() .map(person -> Stream.of(person, person.dolly())) .collect(Collectors.toList());
1. map creates a new Stream containing two people2. This Stream is then added to the main Stream as-is, leaving
us with a pretty useless: List<Stream<Person>>
STREAMS EXAMPLE 6 - FLATMAPList<Person> clonedPeople2 = loadsOfPeople.stream() .flatMap(person -> Stream.of(person, person.dolly())) .collect(Collectors.toList());
1. �atMap combines each element from the new Streams intoone Stream<Person>
2. So now we've got what we wanted in the �rst place, aList<Person>
Sweeet!
STREAMS EXAMPLE 7 - REDUCEint totalAgeUsingReduce = loadsOfPeople.stream() .map(Person::getAge) .reduce((total, current) -> total + current) .get(); //get the result from the Optional
This is the same as the previous example, the difference beingwe don't specify a default value for reduceNot specifying a default value means the result is Optional...if the Stream is empty then so is the result!
STREAMS EXAMPLE 8 - GROUPINGMap<Integer, List<Person>> peopleGroupedByAge = loadsOfPeople.stream() .filter(x -> x.getIq() > 110) .collect(Collectors.groupingBy(Person::getAge));
The collect method groups the �ltered results by age,producing a Map<age, <Person>>
{52=[Person{... age=52, iq=113, gender=MALE}], 60=[Person{... age=60, iq=120, gender=FEMALE}], 28=[Person{... age=28, iq=190, gender=MALE}]}
STREAMS EXAMPLE 9 - PARTITIONINGMap<Boolean, List<Person>> peoplePartitionedByAge = loadsOfPeople.stream().filter(x -> x.getIq() > 110) .collect(Collectors .partitioningBy(x -> x.getAge() > 55));
The collect method partitions the �ltered results by ageThe Map will have two entries, true and false, according to thePredicate
{false=[Person{... age=28, iq=190, gender=MALE}], true=[Person{... age=60, iq=120, gender=FEMALE}]}
STREAMS EXAMPLE 10 - MULTIPLE GROUPSMap<Integer, Double> peopleGroupedBySexAndAvgAge = loadsOfPeople.stream() .filter(x -> x.getIq() > 110) .collect( Collectors.groupingBy(Person::getAge, Collectors.averagingInt(Person::getIq)));
We can group by multiple CollectorsHere we group by age and the average IQ of that group
{52=113.0, 60=117.5, 28=190.0}
STREAMS EXAMPLE 11 - FINDANYloadsOfPeople.stream() .filter(t -> t.getGender() == Person.Sex.FEMALE) .findAny() .ifPresent(System.out::println);
findAny either returns an element or nothing, hence we getan OptionalifPresent executes the Lambda if we get a result
STREAMS EXAMPLE 12 - PARALLELLets iterate over 10,000,000 elements!
x -> Stream.iterate(1L, i -> i + 1) .limit(x) .reduce(Long::sum).get();
Executes in 80ms - we incur a penalty here because the long isrepeatedly boxed and unboxed
Executes in 211ms?! It turns out parallel isn't always a free win!
x -> Stream.iterate(1L, i -> i + 1) .parallel().limit(x) .reduce(Long::sum).get();
STREAMS EXAMPLE 13 - PARALLEL WIN!x -> LongStream.rangeClosed(1L, x) .reduce(Long::sum).getAsLong();
Executes in 24ms - much better using an unboxed Stream
Executes in 7ms - now that the Stream isn't dynamic, parallelworks much better!
x -> LongStream.rangeClosed(1L, x) .parallel() .reduce(Long::sum).getAsLong();
OPTIONALUse Optional instead of passing null around, helps preventNullPointerExceptionsOptional is a container that’s either empty or present, inwhich case it contains a valueSo anytime that you’re thinking of return or accepting a nullvalue in a method, use Optional instead!
public class Computer { private Optional<Mainboard> mainboard; } public class Mainboard { private Optional<Cpu> cpu; } public class Cpu { private String type; }
USING OPTIONALSeveral ways to create an Optional:
Optional.of(cpu); //Throws an NPE if cpu is null Optional.ofNullable(cpu); //empty if cpu is null
Getting the contents from the Optional:
cpu.get(); //get CPU or throw NoSuchElementException cpu.orElse(new Cpu()); //safe get, provides default
And more...
cpu.isPresent(); //true if present, false if empty cpu.ifPresent(x -> System.out.println(x.getType()));
Also supports map, �atMap and �lter!
OPTIONAL EXAMPLE 1 - BASICSif (mainboard.isPresent() && mainboard.get().getCpu().isPresent()) { mainboard.get().getCpu().get().getType(); }
Eww! Lets try something else!
***Fails to compile, calling getType on an Optional... if onlywe could �atten it???
Optional<String> cpuType = mainboard.map(Mainboard::getCpu) .map(Cpu::getType); //Optional<Optional<Cpu>>
OPTIONAL EXAMPLE 2 - FLATMAPOptional<String> stringOptional = mainboard .flatMap(Mainboard::getCpu) .map(Cpu::getType);
Lets take this further and safely get cpu type of a Computer!
Computer computer = new Computer(mainboard); String cpu = computer.getMainboard() .flatMap(Mainboard::getCpu) .map(Cpu::getType) .filter(x -> "Intel".equals(x)) .orElse("Nothing we're interested in!");
IT'S BEEN EMOTIONAL...Slides at Source at Follow me
markglh.github.io/Java8Madlab-Slidesgithub.com/markglh/Java8Madlab-Examples@markglh
RECOMMENDED READINGLambdas Part 1 (Ted Neward)Lambdas Part 2 (Ted Neward)Lambda Expressions vs Method References (Edwin Dalorzo)Youtube: Java 8 - more readable and �exible code (Raoul-Gabriel Urma)Youtube: Lambda Expressions & Streams (Adib Saikali)Java 8 Streams Part 1 (Raoul-Gabriel Urma)Java 8 Streams Part 2 (Raoul-Gabriel Urma)Optional - No more NPE's (Raoul-Gabriel Urma)
Top Related