Lambda, Streams & Stream APIs

Lambda Expressions

Lambda is anonymous function ( function with no name & not being bounded to an identifier).
So what's the big deal?
Everything in Java is around objects so functions cannot exists with objects but with lambda around, you can have functions without any objects and you can pass them as arguments.

Syntax :
(parameters) -> expression

(parameters) -> {statement1; .... ; statementN;}

() -> expression

Functional Interface

It is an interface with exactly one abstract method. You would actually pass in implementation of method as a lambda expression.
For example, java.util.function package provides numerous functional interfaces.

Consumer is a functional interface used for foreach loop.

You can write your own functional interface too.

Lambda Illustrated

For example :

(a,b) -> a + b


integerList.foreach(x -> { if (x > 2) call1(x); else call2(x); });


Static Method Reference
List<String> names = Arrays.asList("name1","name2","name3");

names.forEach(System.out::println);
names.forEach(StringUtils::capitalize);

Instance Reference

method can be invoked using instance reference directly as:
instanceReference::methodname

Constructor Reference 

Constructor can be invoked as:
ClassName::new

Unbound non-static reference(By Return Type)
For example ,
String class method toLowerCase()
Usage  :   ClassName :: methodName

String str ="abc";

Now, toLowerCase can be invoked as 
s-> s.toLowerCase()
s:: toLowerCase
String ::toLowerCase

======================================

Stream

Process collection of objects . This method can be invoked on any Class that implements Collection Interface. 

Stream has a source, intermediate & Terminal operation.


Collection.stream() - Returns Sequential Stream

Collection.parallelStream() - Returns Parallel Stream( Can also be used for sequential stream)

Stream can also be created as :
1> Empty Stream 
Stream.empty() - > Empty String


2> Stream.generate(Supplier)
Stream.generate(new Random()::nextInt).limit(15)
15 elements generated


3> Stream.iterate(Seed, UnaryOperator)
Stream.iterate(0, n->n+1).limit(20)

4> Stream.of()
Stream.of(1) or Stream.of("a","b","c")

5> Arrays.stream
String[] arr = new String[]{"str1","str2","str3"};
Arrays.stream(arr)

6> Stream.builder()
Stream.<String>builder().add("str1")
.add("str2").build();

7> From Primitives 
IntStream.rangeClosed(startInclusive,endInclusive)
IntStream.range(startInclusive,endExclusive)

8> From file 
Files.lines(filePath)

9> From String 
Pattern.compile(";").splitAsStream("a";"b";"c")
String str = "some String";
IntStream it = str.chars();

10> Concatenates 2 Streams
Stream.concat(stream1, stream2)

Something Important about Stream

Stream is one time kind of operation so once executed, it cannot be referenced again unless it is re-executed.
For example,

Stream <Integer> integerStream = Stream.of(1, 2,3)
.filter(x -> x > 1);
integerStream.count() ; //executes well
//At this point stream has already reached its final stage or //terminal  hence following statement will throw exception

integerStream.findAny() ;// throws IllegalStateException

WorkAround :
List<Integer> integerList = Stream.of(1, 2,3)
.filter(x -> x > 1).collect(Collectors.toList());
integerList.stream().count();
integerList.stream().findAny() ;// Valid

Stream Operations 

Returning Stream:
Let's look at methods that returns back another stream ( Please also look at Static Methods returning Stream above) :

stream.filter(Predicate)

Examples :
strlist.stream().filter(str -> str.matches("MyName")).collect(Collectors.toList());

Get a Employee if Found :
employeeList.stream().filter(emp-> emp.getName().equals("Milind"))
.findAny()     //Return a Employee Wrapped under Optional
.OrElse(null);


employeeList1.stream()
.filter(employeeList2::contains).collect(Collectors.toList());
Here Employee must override equals & hashcode otherwise it will return empty as they are from different memory storage 


stream.map(function)

employeeList.stream().filter(
emp-> emp.getName().equals("Milind"))
.map(Employee::getName)
.findAny() //Returns employee's name under Optional
.orElse(null);

employeeList.stream()
.map(Employee::getName)
.collect(Collectors.toList())

stream.flatmap(function)
Basically flats the array/set/list into Stream of that Object

employeeList.stream()
.map(x -> x.getPreviousEmployers()) //Stream<List<Employers>>
.flatmap(x-> x.stream())
.distinct()
.collect(Collectors.toList());


stream.limit(number)
employeeList.stream()
.sorted().limit(1).findAny();

stream.distinct()
employeeList.stream()
.distinct().collect(Collectors.toList());

stream.peek(someaction)

employeeList.stream()
.map(Employee:: getName)
.peek(System.out::println);


stream.skip(firstn)
employeeList.stream()
.skip(2)..map(Employee:: getName)
.peek(System.out::println);


stream.sorted(), stream.sorted(Comparator)
employeeList.stream()
.sorted().limit(1).findAny();

Returning Actual Object or wrapped in Optional:
stream.findAny()  - Optional<T>

employeeList.stream()
.sorted().limit(1).findAny();

stream.findFirst()- Optional<T>

employeeList.stream()
.sorted().limit(1).findFirst();

stream.max(Comparator)- Optional<T>
Integer maxEmpId = 
employeeList.stream()
.map(Employee::getId)
.max(Integer :: compare)
.get();




stream.min(Comparator)- Optional<T>

Integer minEmpId = 
employeeList.stream()
.map(Employee::getId)
.min(Integer :: compare)
.get();

stream.reduce(binaryOperator)     - Optional<T>

employeeList.stream()
.filter(emp -> emp.getDept().equals("IT")
.map(Employee::getSalary)
.reduce((x,y)-> x+y).
.ifPresent(System.out::println);


stream.reduce(identity,binaryOperator) - T

long initialValue= 10000L; //Misc Expenses
long totalExpenses = employeeList.stream()
.filter(emp -> emp.getDept().equals("IT")
.map(Employee::getExpenses)
.reduce(initialValue, (x,y)-> x+y).


Returning Other Types :
stream.reduce(identity,bifunction,binaryOperator) - IdentityType

int[] intArray = {4,3,2};
int result = 
Arrays.stream(intArray)
.boxed()
.reduce(1, (a,b) -> a * b,  (d,e)-> d* e);

System.out.println(result); //prints 24

int result = 
Arrays.stream(intArray)
.boxed()
.parallel()
.reduce(1, (a,b) -> a * b,  (d,e)-> d* e);


stream.collect (Collector<R>) - R

Map<Employee.Gender, Long> byGenders = employeeList.stream()
.filter(emp -> emp.getDept().equals("IT")
.collect(Collectors.groupingBy(x-> x.getGender(), Collectors.counting()));

stream.collect(Supplier, accumulator, combiner) - R

Example1: 

List<String> list = Arrays.asList("s1", "s2", "s3","s4","s5","s6");
List<String> list1 = list.stream().map(Object::toString).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
System.out.println(list1);


String s = list.stream().collect(StringBuilder::new,
                (sb, s1) -> sb.append(",").append(s1),
                (sb1, sb2) ->  sb1.append("->").append(sb2.append("]"))).toString();
System.out.println(s);

Output : ,s1,s2,s3,s4,s5,s6


String s = list.parallelStream().collect(StringBuilder::new,
                (sb, s1) -> sb.append(",").append(s1),
                (sb1, sb2) ->  sb1.append("->").append(sb2.append("]"))).toString();
System.out.println(s);

Output : ,s1->,s2->,s3]]->,s4->,s5->,s6]]]


stream.allMatch(Predicate) - boolean

stream.anyMatch(Predicate) - boolean

stream.noneMatch(Predicate) - boolean


Other Operations:
stream.count()    - long 

stream.forEach(Consumer action)  - void
stream.forEachOrdered(Consumer action) - void

stream.ToArray() - Object[]
stream.toArray(IntFunction A[]) - A[]









Additional : 
stream.mapToDouble/ToInt/ToLong
stream.flatmapToDouble/ToInt/ToLong






















No comments:

Post a Comment