exploring streams and lambdas in java8
TRANSCRIPT
Exploring Streams and Lambdas in Java8
• Presented By:Isuru Samaraweera
Agenda• Why change Java again?• What is FP & Lambda?• Functional Interfaces• Streams• Reduction• Overloading Lambdas• Advanced Collections and collectors• Partioning and Grouping data• Data Parrellelism• Testing Lambdas
Why change java again
• Rise of the multicore CPUS• Algorithms involves locks error-prone time
consuming• Util.concurrent libraries have limititaions• Lack of efficient parrelel operations on a
collection• Java8 allows complex collection-processing
algorithms
What is fp
• Oop data abstraction/side efects• Functional focuses on side effect free• Pure functions/lambdas• Pass functions around easeir to write lazy code
which initialises values when necessary• n -> n % 2 != 0; • (char c) -> c == 'y';• (x, y) -> x + y; • (int a, int b) -> a * a + b * b;
Functional Interfaces
• How does lambda expressions fit into Javas type system?
• Each lambda corresponds to a given type, specified by an interface
• exactly one abstract method declaration• Interface with single abstract method used as
type • Multiple optional default methods
Define a functional interface@FunctionalInterfacepublic interface Calculator { abstract int calculate(int x,int y);}public class FPDemo {public static void main(String[] args) {Calculator f=(x,y)->(x+y);int z = f.calculate(3, 4);System.out.println(z);
test((p,q)->p*q);}public static int test(Calculator cal) {Return cal.calculate(4, 8);}
Lambda Scopes
• int k=0;• Calculator c1=• (int x, int y)->• {System.out.println(k);return x+y;};• k=8;//fail to compile• K is implicitly final• Final is optional
Important functional interfaces in Java
• public interface Predicate<T> { boolean test(T t); }• public interface Function<T,R> { R apply(T t); } • public interface BinaryOperator<T> { T apply(T left, T right); } public interface Consumer<T> { void accept(T t); } • public interface Supplier<T> {T get(); }
Predicate• package com.java8.general;
• import java.util.Objects;• import java.util.function.Predicate;
• public class PredicateTest {
• public static void main(String[] args) {• Predicate<String> predicate = (s) -> s.length() > 0;• boolean s=predicate.test("foo"); // true• predicate.negate().test("foo");
• Predicate<Boolean> nonNull = Objects::nonNull;• Predicate<Boolean> isNull = Objects::isNull;
• Predicate<String> isEmpty = String::isEmpty;• Predicate<String> isNotEmpty = isEmpty.negate();• }
• }
Functions
• Functions accept one argument and produce a result.
• Function<String, Integer> toInteger = Integer::valueOf;
• Function<String, Integer> toInteger=(s->Integer.valueOf(s);
Suppliers• Suppliers produce a result of a given generic type. Unlike
Functions, Suppliers don't accept arguments.• public class SupplierTest {
• public static void main(String[] args) {
• Supplier<SupplierTest> personSupplier = SupplierTest::new;• personSupplier.get(); // new Person• }• }
Consumers
• consumers represents operations to be performed on a single input argument.
• Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
• greeter.accept(new Person("Luke", "Skywalker"));
Comparators
• Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); Person p1 = new Person("John", "Doe"); Person p2 = new Person("Alice", "Wonderland");
• comparator.compare(p1, p2); // > 0
Type inference
• Predicate<Integer> atleast5=x->x>5;
• BinaryOperator<Long> addlngs=(x,y)->x+y;
• BinaryOperator add=(x,y)->x+y;
Streams
• A stream represents a sequence of elements and supports different kind of operations to perform computations upon those elements:
• List<String> myList =• Arrays.asList("a1", "a2", "b1", "c2", "c1");• myList.stream().filter(s -> s.startsWith("c"))• .map(String::toUpperCase) .sorted()• .forEach(System.out::println);
Traditional external iteration
• Int count=0;• Iterator<Artist> iterator=allartists.iterator()• While(iterator.hasNext())• {Artist artist=iterator.next();• If(artist.isForm(“NY”)• Count++• }• Lot of boilerplate code and difficult concurrency• Serial drawback
Internal Iterator with streams
• Long count=allartists.stream().filter(artist->artist.isFrom(“NY”)).count();
• Stream is tool for building up complex operations on collections using functional approach
• If return is a stream its lazy-Intermediete stream• If returns a value or void then its eager-Terminal
value
Terminal vs Lazy operations• //does nothing• artlist.stream().filter(artist->artist.isFrom("India"));• //does nothing lazy inilitation• artlist.stream().filter(artist-
>{System.out.println(artist.getName());• return artist.isFrom("India");});• long x=artlist.stream().filter(artist-
>{System.out.println(artist.getName());• return artist.isFrom("India");}).count();
• System.out.println("x is"+x);
Common stream operations
• Collect(toList())• Eager operation that genertes a list from the
values in a stream• List<String>
collected=Stream.of("A","b","c").collect(Collectors.toList());
• Streams are lazy so u need eager operation like collect
map
• Traditional uppercase conversion• List<String> collected=new ArrayList<>();• For(String string:asList(“a”,”b”,”c”)){• String upper=string.toUpperCase();• Collected.add(upper);• }• List<String>
mapped=Stream.of("A","b","c").map(string->string.toUpperCase()).collect(Collectors.toList());
filter
• Assume search strings start with a digit• Traditional style-For loop and iterate• Functional style• List<String>
begwithn=Stream.of(“a”,”1abc”,”abc1”).filter(value->isDigit(value.charAt(0))).collect(toList());
• Predicate interface returns true/false
sorted
• stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println);
• Sorted is an intermediate operation which returns a sorted view of the stream. The elements are sorted in natural order unless you pass a custom Comparator.
Map and Match
• stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println);
• boolean anyStartsWithA = stringCollection .stream() .anyMatch((s) -> s.startsWith("a"));
flatmap
• Replace a value with a stream and concantenate all streams together
• List<Integer> together=Stream.of(Arrays.asList(1,2),Arrays.asList(3,4)).flatMap(numbers->numbers.stream()).collect(toList());
• Flatmap return type is a stream
Max and min
• List<Track> tracks=Arrays.asList(new Track("track1",524),new Track("track2",454),new Track("track3",444));
• Track shortesttrack=tracks.stream().min(Comparator.comparing(track->track.getLength())).get();
• Comparing builds a comparator using keys
Reduction Operations
• Terminal operations ( average, sum, min, max, and count) that return one value by combining the contents of a stream
• reduction operations that return a collection instead of a single value.
• general-purpose reduction operations reduce and collect
ReduceOptional<T> reduce(BinaryOperator<T> accumulator)Performs a reduction on the elements of this stream, using
an associative accumulation function, and returns an Optional describing the reduced value, if any.
T reduce(T identity, BinaryOperator<T> accumulator)Performs a reduction on the elements of this stream, using the provided identity value and an associative accumulation function, and returns the reduced value.
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)Performs a reduction on the elements of this stream, using the provided identity, accumulation and combining functions.
Reduce with BinaryOperator
• persons• .stream()• .reduce((p1, p2) -> p1.age > p2.age ? p1 :
p2)• .ifPresent(System.out::println); • // Pamela• Returns Optional
Reduce with identity and accumilator
• Integer totalAgeReduce = roster• .stream()• .map(Person::getAge)• .reduce(• 0,• (a, b) -> a + b); • identity: The identity element is both the initial value of the reduction and
the default result if there are no elements in the stream
• accumulator: The accumulator function takes two parameters: a partial result of the reduction (in this example, the sum of all processed integers so far) and the next element of the stream (in this example, an integer).
(a, b) -> a + b
Generic reduce• a reduce operation on elements of type <T> yielding a result
of type <U>• <U> U reduce(U identity, BiFunction<U, ? super T, U>
accumulator, BinaryOperator<U> combiner);• identity element is both an initial seed value for the
reduction and a default result if there are no input elements• The accumulator function takes a partial result and the next
element, and produces a new partial result• The combiner function combines two partial results to
produce a new partial result.
• List<String> test= new ArrayList<String>();• test.add("isuru");• test.add("sam");• test.add("silva");• int s = test.stream().reduce(0, (x, y) -> x + y.length(), (x, y) -> x + y);• - identity - identity value for the combiner function
- reducer - function for combining two results- combiner - function for adding an additional element into a result.
• When you run the stream in parallel, the task is spanned into multiple threads. So for example the data in the pipeline is partitioned into chunks that evaluate and produce a result independently. Then the combiner is used to merge this results.
Putting all together
• Get all artists for album• Figure out which artists are bands• Find the nationalities for each band• Put together a set of these values
• Set<String> origins=album.getMusicians().filter(artist->artist.getName().startsWith(“The”)).map(artist->artist.getNationality()).collect(toSet());
• Filter,map are lazy operations• Collect is eager
Stream misuse• List<Artist> musicians=album.getMusicians().collect(toList());• List<Artist> bands=musicians.stream().filter(artist-
>artist.getName().startsWith(“The”)).collect(toList());• Set<String> origins=bands.stream.map(artist-
>artist.getNationality().collect(toSet());• Its harder to read /boiler plate code• Less efficient because it requires eagerly creating new collection
objects in each intermediate step• Clutters the code with intermediate variables• Multithreading/parrelllism issues• Chain them
Overloading• private interface IntegerBiFunction extends
BinaryOperator<Integer>{}• public void overloadedm1(BinaryOperator<Integer> lambda)• {• System.out.println("Binaryoperator");• }
• public void overloadedm1(IntegerBiFunction lambda)• {• System.out.println("Bifunction");• }
• public void test()• {• overloadedm1( (y,x)->x+1);• }• If there are several possible target types the
most specific type is inferred
• private interface IntPredicate• {• public boolean test(int value);• }• public void overloadp1(Predicate<Integer> predicate)• {• System.out.println("Predicate");• }
• public void overloadp1(IntPredicate predicate)• {• System.out.println("Intpredicate");• }• If there are several possible target types and there is no specific type you have to manually
provid the type
Binary interface compatibility
• Backward binary compatibility-If you compile app in Java1 to 7 it will run out of the box in Java8
• Stream method added to java8 Collection iface
• Breaks the binary compatibility• Not compile or Exception by Calssloader
Advanced Collections and Collectors
• Method references• Artist:getName• Artist:new• String[] new
Element Ordering
• List<Integer> numbers=Arrays.asList(2,3,4);• List<Integer>
sameOrder=numbers.stream().collect(Collectors.toList());
• Set<Integer> numberset=new HashSet<>(Arrays.asList(3,4,5,6));
• List<Integer> ntsameOrderset=numberset.stream().collect(Collectors.toList());
Collector
• toList ,toSet you done give the complete implementation
• Colelcting values into a collection of specific type
• Stream.collec(toCollection(TreeSet ::new))
To Values
• Collect into a single value using collector• Finding the band with most numbers• public Optional<Artist>
biggestGroup(Stream<Artist> artists)• {• Funtion<Artist,Long> getCount=artist-
>artist.getMembers().count();• Return artists.collect(maxBy(comparing(getCount)));• minBy also there
Partioning the data
• Split out a list• Public Map<Boolean,List<Artist>>
bandsAndsolo(Stream<Artist> artists)• {• return artists.collect(partitionBy(artist-
>artist.isSolo()));• }• Or partitionBy(Artist::isSolo) method reference
Grouping the data
• Grouping albums by main artist• Public Map<Artist,List<Album>>
albumsByArtists(Stream<Album> albums)• {• Return albums.collect(groupingBy(album-
>album.getMainMusician()));• }
String• Traditional String handling• StringBuilder builder=new StringBuilder(“[“);• For(Artist artist:artists)• {• If(builder.length()>1)• Builder.apppend(“, “);• }• String name=artist.getName();• Builder.append(name);• }• Builder.append(“]”);• String result=builder.toString();
• String result=artists.stream().map(Artist::getName).collect(Collectors.joiniing(“, “, “[“,”]”));
Composing collectors
• Naïve approach• Map<Artist,List<Album>>
albumByArtist=albums.collect(groupingBy(album->album.getMainMusician())));
• Map<Artist,Integer> numberofalbums=new HashMap<>();
• For(Entry<Artist,List<Album> entry:albumsByArtist.entrySet()){
• Numberofalbums.put(entry.getKey(),entry.getValue().size());
Using collectors to count the number of albums for each artist
• Public Map<Artist,Long> numberOfalbums(Stream<Album> albums)
• {Return albums.collect(groupingBy(album->album.getMusician(),counting())));
• }• This grouping devides elements into buckets• Reduction as a collector
Data paralleism
• Parellism vs Concurrency• Concurrency arises when 2 tasks are making
progress at overlapping time periods• Parrellism arises 2 tasks are hapenning at
same time –multicore cpu• In single cpu-concurrency• Multi core-concurrent/parrellel
Data parellism..contd
• Splitting up the data to be operated on and assigning single processing unit to each chunk of data
• Perform same operation on a large dataset• Task parellism-each individual thread of
execution can be doing totally different task• Java EE container TP
Parellel stream operations• Serial summing of album trak lengths• Public int serialArraySum(){• Return
album.stream().flatmap(Album::gettracks).mapToInt(track:getLength).sum();
• }• Public int parreelelArraySum(){• Return
albums.parallelstream().flatMap(Album::gettracks).mapToInt(Track::getLength).sum();
• When 10000 albums are hit parrel code is faster
Testing Lambdas
• Public static List<String> allToUppercase(List<String> words)
• {
• Return words.stream().map(string->string.toUpperCase()).collect(Collectors.<String>.toList());
• }
• @Test• Public void multiplewordsToUpperCase()• {• List<String> input=Arrays.asList(“a”,”b”,”hello”);• List<String> result=Testing.allToUppercase(input);• assertEquals(asList(“A”,”B”,”HELLO”),result);• }