java 8 by example!

53
JAVA 8 - BY EXAMPLE! PATTERNS AND PRACTICES FOR LAMBDAS, STREAMS, OPTIONAL... Created by / Mark Harrison @markglh

Upload: mark-harrison

Post on 11-Feb-2017

72 views

Category:

Software


5 download

TRANSCRIPT

Page 1: Java 8 by example!

JAVA 8 - BY EXAMPLE!PATTERNS AND PRACTICES FOR LAMBDAS, STREAMS,

OPTIONAL...Created by / Mark Harrison @markglh

Page 2: Java 8 by example!

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 } }

Page 3: Java 8 by example!

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

Page 4: Java 8 by example!

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)

Page 5: Java 8 by example!

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);

Page 6: Java 8 by example!

WAT?!?

Page 7: Java 8 by example!

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

Page 8: Java 8 by example!

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); } } }

Page 9: Java 8 by example!

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());

Page 10: Java 8 by example!

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));

Page 11: Java 8 by example!

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();

Page 12: Java 8 by example!

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"

Page 13: Java 8 by example!

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

Page 14: Java 8 by example!

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 }

Page 15: Java 8 by example!

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());

Page 16: Java 8 by example!

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())));

Page 17: Java 8 by example!

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));

Page 18: Java 8 by example!

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

Page 19: Java 8 by example!

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

Page 20: Java 8 by example!

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);

Page 21: Java 8 by example!

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));

Page 22: Java 8 by example!

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 */ }

Page 23: Java 8 by example!

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);

Page 24: Java 8 by example!

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);

Page 25: Java 8 by example!

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}

Page 26: Java 8 by example!

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()

Page 27: Java 8 by example!

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

Page 28: Java 8 by example!

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

Page 29: Java 8 by example!

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

Page 30: Java 8 by example!

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

Page 31: Java 8 by example!

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!

Page 32: Java 8 by example!

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, ...

Page 33: Java 8 by example!

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

Page 34: Java 8 by example!

twoEvenSquares = List[4, 16]

Page 35: Java 8 by example!

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

Page 36: Java 8 by example!

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

Page 37: Java 8 by example!

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>

Page 38: Java 8 by example!

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>>

Page 39: Java 8 by example!

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!

Page 40: Java 8 by example!

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!

Page 41: Java 8 by example!

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}]}

Page 42: Java 8 by example!

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}]}

Page 43: Java 8 by example!

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}

Page 44: Java 8 by example!

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

Page 45: Java 8 by example!

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();

Page 46: Java 8 by example!
Page 47: Java 8 by example!

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();

Page 48: Java 8 by example!

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; }

Page 49: Java 8 by example!

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!

Page 50: Java 8 by example!

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>>

Page 51: Java 8 by example!

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!");

Page 52: Java 8 by example!

IT'S BEEN EMOTIONAL...Slides at Source at Follow me

markglh.github.io/Java8Madlab-Slidesgithub.com/markglh/Java8Madlab-Examples@markglh

Page 53: Java 8 by example!

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)