Java 8 Notes
Java 8 Notes
➢ Lambda Expressions
Lamda enables Functional Programming - This is the advantage of Lamda in Java 8
Lamda enables support for parallel processing
What is Functional Programming ? - Till now in Java we passed data as method argument. Passing the
functionality as method argument is called as Functional programming
Example: show(()->System.out.println("hi")); Here show is method. We are passing the lamda as
parameter. Earlier we have to pass the object and on this object call the method
In object-oriented programming, data is stored in objects
In functional programming, data cannot be stored in objects, and it can only be transformed by
creating functions
How to Write a Lamda in Java ?
Example: We have an interface called Test like below
Public interface Test{
Public void display()
Here how did we write this statement -> as I mentioned each lamda corresponds to one
interface so Since we are writing the lamda for Test interface we have mentioned the Left side
like Test testLamda
The java8 compiler looks at the abstract method present in the test interface and accepts the
arguments and return the data accordingly. Since the java compiler is intelligent enough to refer
the abstract method no need to write the method name, argument and return type
That means first write like this Test testLamda=public void display(){
System.out.println("Hello");
}
Later according to the above point remove the method name, parameters and return type and
access modifier because compiler internally refers to abstract method in interface
Here display method is not taking any arguments hence empty brace()
And after that if we want to perform any logic we need to write -> symbol. If the code
implementation is in 2 lines we can use curly brace or else not needed
Every lamda corresponds to Interface and that interface should have only one abstract method
If there are more abstract methods then Java 8 compiler doesn't know which abstract method it
should refer. Hence we should allow only one abstract method per interface. In order to facilitate this
feature the corresponding interface should be marked as @FunctionalInterface.
In this way if we are writing the lamda then we need to create n number of interfaces so to avoid this
Java 8 has introduced java.util.functions package which has 4 types of interfaces and we can make
use of those based on the business scenario.
Example of lamda expression: The below is double abstract method present in Test1 interface then
how can I write the lamda. (below is the double method implementation)
Lamda can replace the anonymous inner class. Example of creating the thread
Advantage of Lamda is,
It enables functional programming. (Functional programming means sending the functionality as
method argument)
We can replace the anonymous inner class with Lamda expression
➢ Functional Interfaces
Functional interface is something in which there is only 1 abstract method is present and it can have
any number of default or static methods.
To make or denote the interface as functional interface, we have to annotate it as
@FunctionalInterface annotation
Once we make the interface annotate with @FunctionalInterface then we cannot add more than 1
abstract method.
@FunctionalInterface annotation is a facility to avoid accidental addition of abstract methods in the
functional interfaces. It’s optional but good practice to use it.
Functional interface corresponds to lamda expression
Java 8^J9 and 11 Page 2
Functional interface corresponds to lamda expression
A new package java.util.function with bunch of functional interfaces are added to provide target
types for lambda expressions and method references
From Java 8 onwards, lambda expressions can be used to represent the instance of a functional
interface
Java SE 8 included four main kinds of functional interfaces which can be applied in multiple situations.
These are:
Consumer
Predicate
Function
Supplier
Consumer, Predicate, and Function, likewise have additions that are provided beneath –
Consumer -> Bi-Consumer
Predicate -> Bi-Predicate
Function -> Bi-Function
Consumer
The consumer interface of the functional interface is the one that accepts only one argument. It
returns nothing
void accept(T paramT);
Bi-Consumer
The Bi-consumer interface of the functional interface is the one that accepts two arguments. It
returns nothing
void accept(T paramT, U paramU);
Predicate
A function that accepts an argument and, in return, generates a boolean value as an answer is
known as a predicate
boolean test(T paramT);
Bi-Predicate –
Bi-Predicate is also an extension of the Predicate functional interface, which, instead of one,
takes two arguments, does some processing, and returns the boolean value
boolean test(T paramT, U paramU);
Function
A function is a type of functional interface in Java that receives only a single argument and
returns a value after the required processing
R apply(T paramT);
Bi-Function –
The Bi-Function is substantially related to a Function. Besides, it takes two arguments, whereas
Function accepts one argument.
R apply(T paramT, U paramU);
Supplier
The Supplier functional interface is also a type of functional interface that does not take any
input or argument and yet returns a single output
T get();
There is no Bi Supplier because as it doesn't take any argument and any method can have 1
return type so bi supplier is not possible
➢ Type Inference
Type inference is a Java compiler's ability to look at each method invocation and corresponding
declaration to determine the type argument (or arguments) that make the invocation applicable.
➢ Method Reference:
There are following types of method references in java:
Reference to a static method.
Reference to an instance method.
Reference to a constructor
Look at the below code if you are using without method reference then u need to enable the commented
code. If you are using method reference then not required.
Example 2:
The above code shows the lamda expression for runnable interface. Passing the Runnable instance to the
Thread class
In this above example we can replace the lamda with method expression like below
Here also same thing when you implement run method inside run method you will call the display
method.
In the above code, To write the method reference Write the class name followed by two colon and then
method name (ThreadExample is the class Name:: display)
That means ThreadExample::display is same as ()->display()
Here in this example, lamda doesn't take any arguments and the method doesn't take any arguments
Syntax:
ContainingClass::staticMethodName
➢ forEach(): This forEach method takes the argument as Consumer interface. That means we can write lamda
and send lamda as a argument to this method.
Example without Lamda
In the above example no need to pass what type of e object is, since we are calling the forEach on the
collection object, compiler will understand what type of objects that collection will store.
➢ removeif():
The removeIf() method of ArrayList is used to remove all of the elements of this ArrayList that satisfies a given
predicate filter which is passed as a parameter to the method
We can create a stream from a source, perform operations on it and produce the result. Source may
be a collection or an array or an I/O resource. Stream does never modify the source.
Remember that Streams doesn’t store the data. We can’t add or remove elements from streams.
Therefore, we can’t call them the data structures. They do only operations on the data.
Stream’s operations are primarily divided in to two categories : Intermediate operations & Terminal
operations.
Stream works on a concept of ‘Pipeline of Operations’. A pipeline of operations consists of three
things : a source, zero or more intermediate operations and a terminal operation.
In order to gain the performance while processing the large amount of data, Stream has a concept of
parallel processing without writing any multi-threaded code.
All elements of a stream are not populated at a time. They are lazily populated as per demand
because intermediate operations are not evaluated until terminal operation is invoked.
A stream is represented by the java.util.stream.Stream<T> interface. This works with objects only.
There are also specializations to work with primitive types, such as IntStream, LongStream,
and DoubleStream
If we want to use the concept of streams then stream() is the method to be used. Stream is available
as an interface.
Stream s = c.stream();
Here C represents collection object.
• Example 2 :- another example is finding ranks of Student from the input List of Students
• Note :- Number of elements returned in the new Stream after applying map function will always
be equal to number of elements in the Original Stream
➢ distinct()
• Returns a stream consisting of the distinct elements (according to Object.equals(Object)) of this
stream.
• For ordered streams, the selection of distinct elements is stable (for duplicated elements, the
element appearing first in the encounter order is preserved.) For unordered streams, no stability
guarantees are made.
• Syntax:
1 Stream distinct()
flatMap()
• This Stream method is an intermediate operation which is stateless and non-interfering with other elements in the
Stream
• map method does only transformation; but flatMap does mapping as well as flattening and this is main difference
between these 2 map method of Stream API
• Suppose, we have List of List of String elements, in this case direct transformation isn’t possible. So, we have to map first
and then flatten to get List of String elements
• Transformation and Flattening :- When flatMap function is applied to Stream of Stream of T type (Stream<Stream<T>>)
then it get converted to Stream of R type (Stream<R>) i.e.; transform to another stream and next flatten it
• One-to-Many mapping :- flatMap() method produces stream of values for each of the elements in the input stream hence
it is referred as One-to-Many mapping
• Note :- Number of elements returned in the new Stream after transformation and flattening will always be equal to sum of
elements in all sub-Streams
• Method signature :- <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
Flattening
➢ sorted()
• Returns a stream consisting of the elements of this stream, sorted according to the natural order.
• If the elements of this stream are not Comparable, a java.lang.ClassCastException may be thrown
when the terminal operation is executed.
• Note: For ordered streams, the sort is stable. For unordered streams, no stability guarantees are
made.
• We can even pass the comparator to this sorted method to get the desired order
➢ limit()
• Returns a stream with the limited size given. It will truncate the remaining elements from the stream.
• Note: limit() is suitable for sequential streams and cannot give good performance results for parallel
streams.
• Syntax:
1 Stream limit(long maxSize)
➢ skip()
• This method skips the given n elements and returns a Stream. This is the most useful when want to
perform any operations on last n records or lines from a List or Stream.
• Syntax:
1 Stream skip(long n)
➢ Terminal Operations:
Java-8 Stream terminal operations produces a non-stream, result such as primitive value, a collection or
no value at all. Terminal operations are typically preceded by intermediate operations which return
another Stream which allows operations to be connected in a form of a query.
Here is the list of all Stream terminal operations:
toArray()
collect()
count()
➢ Creating Set:
➢ Creating Map:
➢ Creating Set:
➢ forEach():
• forEach() method is introduced in Collection and Map in addition to Stream, so we can iterate
through elements of Collection, Map or Stream
• It is a terminal operation which is used to iterate through all elements present in the Stream
• Performs an action for each element of this stream
• Input to the forEach() method is Consumer which is Functional Interface
• For Sequential stream pipeline, this method follows original order of the source
• But for Parallel stream pipeline, this method doesn’t guarantee original order of the source
Syntax:
void forEach(Consumer action)
➢ Write a Lamda to filter even numbers and print to console using lambda expression
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
➢ count()
• This Stream method is a terminal operation which counts number of elements present in the stream
Method signature :- long count()
Example:
➢ Write a Lamda to Count how many employees are present whose name starts with K.
(Or)
➢ min()
• Returns the minimum value in the stream wrapped in an Optional or an empty one if the stream is empty.
• It is a terminal operation which reads given stream and returns maximum element according to
provided Comparator
• This is the special case of reduction i.e.; Stream.reduce();
• Method signature :- Optional<T> max(Comparator<? super T> comparator)
• The min and Max method accepts a Comparator reference so we can write a lamda for compareTo
method and pass that as a argument to min and max method
➢ Write a Lamda and print the employee who is having minimum salary ?
➢ max()
Java 8^J9 and 11 Page 12
➢ max()
• Returns the maximum value in the stream wrapped in an Optional or an empty one if the stream is empty.
• This Stream method is a terminal operation which reads given stream and returns maximum element
according to provided Comparator
• This is the special case of reduction i.e.; Stream.reduce();
• Method signature :- Optional<T> max(Comparator<? super T> comparator)
➢ Write a Lamda and print the Employee who is having maximum salary ?
• When we talk about primitives, it is easy to know which the minimum or maximum value is. But when
we are talking about objects (of any kind), Java needs to know how to compare them to know which
one is the maximum and the minimum. That's why the Stream interface needs
a Comparator for max() and min()
List<String> strings =
Arrays.asList("Stream","Operations","on","Collections");
strings.stream()
.min( Comparator.comparing(
(String s) -> s.length())
).ifPresent(System.out::println); // on
sum() returns the sum of the elements in the stream or zero if the stream is empty:
➢ skip():
• This Stream method is an intermediate operation which skip/discards first n elements of the given stream
• skip() method returns new stream consisting of remaining elements after skipping specified n elements
from the original stream
• Specified n can’t be negative, otherwise IllegalArgumentException is thrown
• If specified n is higher than the original size of the Stream then an empty Stream is returned
• Method signature :- Stream<T> skip(long n)
➢ limit() method :
• This Stream method is an intermediate operation which limits to first n elements of the given stream
• limit() method returns new stream consisting of elements not longer than the specified max size in the
encounter order
• Specified n can’t be negative, otherwise IllegalArgumentException is thrown
• Stream limit() method is stateful which means it is interfering as it has to keep the state of the items that
are being picked up
• limit() method doesn’t consume entire stream. As soon as, limit reaches the maximum number of
➢ findFirst() method :
• This Stream method is a terminal operation which returns Optional instance describing first element of
the given Stream
• If provided Stream has encounter-order then first element is returned (encounter-order depends on the
source or intermediate operations)
• But if provided Stream has no-encounter-order then any element may be returned
• If provided Stream is empty or intermediate operation‘s result is empty then empty Optional is returned
i.e.; Optional.empty()
• Note: Above behaviour is true for both sequential and parallel streams
➢ findAny() method :
• This Stream method is a terminal operation which returns Optional instance describing any element from
the given Stream
• If provided Stream has encounter-order then most of the times first element is returned but this behavior
isn’t guaranteed
• For Stream with no-encounter-order, any element may be returned
• The behaviour of this operation is explicitly nondeterministic, as it is free to select any element in the
stream. For stable result, use findFirst() method as explained in above section
• If provided Stream is empty or intermediate operation‘s result is empty then empty Optional is returned
i.e.; Optional.empty()
•
• Method signature :- Optional<T> findAny()
➢ Optional
• A NullpointerException is a common issue in java applications. To prevent this, we normally add frequent
NULL checks in our code to check if a variable is not empty before we use it in our
program. Optional provides a better approach to handle such situations
• Every Java Programmer is familiar with NullPointerException. It can crash your code. And it is very hard to
avoid it without using too many null checks. So, to overcome this, Java 8 has introduced a new class
Optional in java.util package. It can help in writing a neat code without using too many null checks. By
using Optional, we can specify alternate values to return or alternate code to run. This makes the code
more readable because the facts which were hidden are now visible to the developer.
• Java Optional class provides a way to deal with null values. It is used to represent a value is present or not.
Java 8 added a new class Optional available in java.util package.
• Advantages of Java 8 Optional:
• Null checks are not required.
• No more NullPointerException at run-time.
• We can develop clean and neat APIs.
• No more Boiler plate code
• We can keep the method return type as Optional.
• Look at the below program where we get the null pointer exception.
• Using the above code we can create the Optional object by calling the OfNullable method.
• OfNullable() : if the value is null then it returns empty optional, if not create the optional object by the
element that we passed as generics.
• isPresent(): This method returns true if the optional is not empty.
• get(): To get the value from the optional we should use the get() method call this method after when
isPresent() returns true;
• ifPresent(Consumer c): This method can be used to perform some operation if the value from the optional
object is not null.
•
• filter(predicate): We can perform the filter operation if the optional is not empty
•
• orElse(): If the value of the optional object is null then using the below method we can return some
default String value. In the below example when the value from the m1() method is null or empty then it
returns "Hi"
• orElseGet(Supplier): here in the below example, m1() method is returning optional and the value inside
optional is null then using the orElseget method we can write the supplier interface lamda and return the
different value. So the output of the below m2 variable is "Value from orElseGet"
now()
Use the now() method to get the current local date-time. Note that we can get the current local timestamp in another
zone by passing the zone id
LocalDateTime now = LocalDateTime.now(); //Current timestamp in UTC LocalDateTime utcTimestamp =
➢ CompletableFuture
As mentioned before, CompletableFuture is one of the most important enhancements in Java 8. It implements the
interfaces Future and CompletionStage, providing several functionalities related to futures out of the box. It is much easier to
handle futures and promises using Java as it was before Java 8
Creation
It is possible to create a completable directly using its constructor:
1 CompletableFuture completableFuture = new CompletableFuture();
2
or using factory methods where the sync or async mode is specified and its task or process is also passed:
1 CompletableFuture completableFuture = CompletableFuture.supplyAsync( ( ) -> {
2 // big computation task
3 return "100";
4 } );
5
Getting results
In order to get the results of a CompletableFuture we have several options:
• Using the get() method:
•
1 System.out.println( "get " + cf.get() );
2
Will wait for ever until the CompletableFuture is completed or cancelled.
• Using the getNow(String fallback) method:
•
1 System.out.println( "get now " + cf.getNow( "now" ) );
2
If the result of the computation is not present yet, the fallback passed as parameter is returned
➢ ExecutorService invokeAll() API
invokeAll() method executes the given list of Callable tasks, returning a list of Future objects holding their
status and results when all complete.
IO Enhancements:
Files.find()