Java 8 Features
Java 8 Features
Lambda Expressions
Functional Interfaces
Method Reference
Streams
Comparable and Comparator
Optional Class
Date/Time API
Default Methods
Factory methods for collections (like List, Map, Set and Map.Entry)
JShell: The Interactive Java REPL
Private Methods in Interfaces
CompletableFuture in Java
Reactive Streams
Java Modules
Lambda Expressions
Lambda Expression basically expresses an instance of the functional interface, in other words, you
can say it provides a clear and concise way to represent a method of the functional interface using
an expression. Lambda Expressions are added in Java 8.
Lambda Expressions implement the only abstract function and therefore implement functional
interfaces. Lambda expressions are added in Java 8 and provide the following functionalities.
Functional Interfaces: A functional interface is an interface that contains only one abstract
method.
Pass and Execute: Pass lambda expressions as objects and execute on demand.
class Test
{
public static void main(String args[])
{
// lambda expression to implement above
// functional interface. This interface
// by default implements abstractFun()
FuncInterface fobj = (int x)->System.out.println(2*x);
Output
10
Components:
Arrow Token (->): Separates the parameter list and the body
Syntax:
Example: The below Java program demonstrates a Lambda expression with zero parameter.
Syntax:
It is not mandatory to use parentheses if the type of that variable can be inferred from the
context.
Example: The below Java program demonstrates the use of lambda expression in two different
scenarios with an ArrayList.
We are using lambda expression to iterate through and print all elements of an ArrayList.
We are using lambda expression with a condition to selectively print even number of
elements from an ArrayList.
class Test {
public static void main(String args[])
{
// Creating an ArrayList with elements
// {1, 2, 3, 4}
ArrayList<Integer> al = new ArrayList<Integer>();
al.add(1);
al.add(2);
al.add(3);
al.add(4);
// Using lambda expression to print all elements of al
System.out.println("Elements of the ArrayList: ");
al.forEach(n -> System.out.println(n));
Note: In the above example, we are using lambda expression with the foreach() method and it
internally works with the consumer functional interface. The Consumer interface takes a single
paramter and perform an action on it.
Syntax:
Example: The below Java program demonstrates the use of lambda expression to implement
functional interface to perform basic arithmetic operations.
@FunctionalInterface
interface Functional {
int operation(int a, int b);
}
Note: Lambda expressions are just like functions and they accept parameters just like functions.
1. () -> {};
2. () -> "geeksforgeeks";
3. () -> { return "geeksforgeeks";};
4. (Integer i) -> {return "geeksforgeeks" + i;}
5. (String s) -> {return "geeksforgeeks";};
1. This lambda has no parameters and returns void. It’s similar to a method with an empty
body: public void run() { }.
4. return is a control-flow statement. To make this lambda valid, curly braces are required
as follows:
5. “geeks for geeks” is an expression, not a statement. To make this lambda valid, you can
remove the curly braces and semicolon as follows: (String s) -> "geeks for geeks". Or if
you prefer, you can use an explicit return statement as follows:
Output
New thread created
Explanation: In the above example, we can see the use of a lambda expression to implement
the Runnable functional interface and create a new thread.
@FunctionalInterface Annotation
@FunctionalInterface annotation is used to ensure that the functional interface cannot have more
than one abstract method. In case more than one abstract method are present, the compiler
flags an "Unexpected @FunctionalInterface annotation" message. However, it is not
mandatory to use this annotation.
interface Square {
int calculate(int x);
}
class Geeks {
public static void main(String args[]) {
int a = 5;
// lambda expression to
// define the calculate method
Square s = (int x) -> x * x;
Explanation: In the above example, the Square interface is annotated as a functional interface.
Then the lambda expression defines the calculate method to compute the square of the number.
Before Java 8, we had to create anonymous inner class objects or implement these interfaces.
Below is an example of how the Runnable interface was implemented prior to the introduction of
lambda expressions.
Example:
Output
New thread created
Since Java SE 1.8 onwards, there are many interfaces that are converted into functional interfaces.
All these interfaces are annotated with @FunctionalInterface. These interfaces are as follows:
1. Consumer
2. Predicate
3. Function
4. Supplier
1. Consumer
The consumer interface of the functional interface is the one that accepts only one argument or a
gentrified argument. The consumer interface has no return value. It returns nothing. There are
also functional variants of the Consumer — DoubleConsumer, IntConsumer, and LongConsumer.
These variants accept primitive values as arguments.
Other than these variants, there is also one more variant of the Consumer interface known as Bi-
Consumer.
This implementation of the Java Consumer functional interface prints the value passed as a
parameter to the print statement. This implementation uses the Lambda function of Java.
2. Predicate
Just like the Consumer functional interface, Predicate functional interface also has some
extensions. These are IntPredicate, DoublePredicate, and LongPredicate. These types of predicate
functional interfaces accept only primitive data types or values as arguments.
Syntax:
3. 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. Many different versions of the function interfaces are
instrumental and are commonly used in primitive types like double, int, long.
Syntax:
Unary Operator and Binary Operator: There are also two other functional interfaces which
are named as Unary Operator and Binary Operator. They both extend the Function and
Bi-Function respectively, where both the input type and the output type are same.
4. 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.
The different extensions of the Supplier functional interface hold many other suppliers functions
like BooleanSupplier, DoubleSupplier, LongSupplier, and IntSupplier. The return type of all these
further specializations is their corresponding primitives only.
Syntax:
result.
class Geeks {
public static void main(String args[]) {
Output
Geek
GeeksQuiz
Geek2
Important Points:
In functional interfaces, there is only one abstract method supported. If the annotation of a
functional interface, i.e., @FunctionalInterface is not implemented or written with a
function interface, more than one abstract method can be declared inside it. However, in
this situation with more than one functions, that interface will not be called a functional
interface. It is called a non-functional interface.
There is no such need for the @FunctionalInterface annotation as it is voluntary only. This is
written because it helps in checking the compiler level. Besides this, it is optional.
An infinite number of methods (whether static or default) can be added to the functional
interface. In simple words, there is no limit to a functional interface containing static and
default methods.
Overriding methods from the parent class do not violate the rules of a functional interface in
Java. In functional interface overriding methods from the parent class does not count as
abstract method.
Method references are used for the following reasons, which are listed below:
Method references enhance readability, which makes the code easier to understand.
It supports a functional programming style that works well with streams and collections.
Note: Functional Interfaces in Java and Lambda Function are prerequisites required in order to
grasp a grip over Method References in Java.
Example:
Output
Geek1
Geek2
Geek3
Explanation: In the above example, we are using method reference to print items. The print
method is a static method which is used to print the names. In the main method we created an
array of names and printing each one by calling the print method directly.
Function as a Variable
In Java 8 we can use the method as if they were objects or primitive values, and we can treat
them as a variable.
Sometimes, a lambda expression only calls an existing method. In those cases, it looks clear to
refer to the existing method by name. The method references can do this, they are compact,
easy-to-read as compared to lambda expressions.
Aspect Syntax
Object :: methodName
Refer to a method in an object
4. Constructor Reference
To look into all these types we will consider a common example of sorting with a comparator
which is as follows:
A static method lets us use a method from a class without writing extra code. It is a shorter way
to write a lambda that just calls that static method.
Syntax:
// Lambda expression
(args) -> Class.staticMethod(args);
// Method reference
Class::staticMethod;
Example:
// Constructor
public Person(String name, int age)
{
// This keyword refers to current instance itself
this.name = name;
this.age = age;
}
// Getter-setters
public Integer getAge() { return age; }
public String getName() { return name; }
}
// Driver class
public class Geeks
{
// Static method to compare with name
public static int compareByName(Person a, Person b) {
return a.getName().compareTo(b.getName());
}
System.out.println();
Output
Sort by Name :
Poonam
Sachin
Vicky
Sort by Age :
Sachin
Vicky
Poonam
Explanation: This example shows how to use static method references to sort items. We have a
person class with attributes like name and age and there are two methods to compare people
by name and by age. In the main method we created a list of people and sorting them by name
and then sort them by age and then printing the name again.
Syntax:
// Lambda expression
(args) -> obj.instanceMethod(args);
// Method reference
obj::instanceMethod;
Example:
class Person {
// Attributes of a person
private String name;
private Integer age;
// Constructor
public Person(String name, int age)
{
// This keyword refers to current object itself
this.name = name;
this.age = age;
}
// Getter-setter methods
public Integer getAge() { return age; }
public String getName() { return name; }
}
// Helper class
// Comparator class
class ComparisonProvider
{
// To compare with name
public int compareByName(Person a, Person b) {
return a.getName().compareTo(b.getName());
}
// Main class
public class Geeks
{
public static void main(String[] args)
{
// Creating an empty ArrayList of user-defined type
// List of person
List<Person> personList = new ArrayList<>();
// Using streams
personList.stream()
.map(x -> x.getName())
.forEach(System.out::println);
System.out.println();
Output
Sort by Name :
Poonam
Sachin
Vicky
Sort by Age :
Sachin
Vicky
Poonam
Explanation: This example show how to use an instance method reference to sort a list of
people. We have created a Person class with name and age and we also created a
ComparisonProvider class, it has methods to compare people by name or age. In the main
method we created a list of people and we are using the ComparisonProvider instance to sort
and print the names first by name, then by age.
It means calling a method on any object that belongs to a certain group or class, not just one
specific object. It helps us write less code when we want to do the same thing for many objects.
Syntax:
// Lambda expression
(obj, args) -> obj.instanceMethod(args);
// Method reference
ObjectType::instanceMethod;
Example:
Output
Poonam
Sachin
Vicky
Explanation: This example shows how to use a method reference to sort a list of names. We
created a list of names and then sorting them ignoring uppercase or lowercase with the help of
compareToIgnoreCase method of the String class and then we are printing the sorted names
It lets us quickly create a new object without writing extra code. It is a shortcut to call the class
new method.
Syntax:
// Lambda expression
(args) -> new ClassName(args);
// Method reference
ClassName::new;
Example:
// Constructor
public Person()
{
Random ran = new Random();
Output
ilvxzcv
vdixqbs
lmcfzpj
dxnyqej
zeqejcn
Explanation: This example shows how to use a constructor method reference to create objects.
We have created a Person class and it gives each person a random name. The getObjectList
method creates a list of objects by using a supplier, which means it uses the Person constructor
to make new Person objects. In the main method we created a list of people and then we are
printing their random names.
Custom utilities: Using predefined methods for frequently used tasks like sorting and
comparisons.
Stream In Java
Stream was introduced in Java 8, the Stream API is used to process collections of objects.
A stream in Java is a sequence of objects that supports various methods that can be pipelined to
produce the desired result.
Java Stream Creation is one of the most basic steps before considering the functionalities of the
Java Stream. Below is the syntax given for declaring a Java Stream.
Syntax
Stream<T> stream;
Here, T is either a class, object, or data type depending upon the declaration.
A stream is not a data structure; instead, it takes input from the Collections, Arrays, or I/O
channels.
Streams don’t change the original data structure, they only provide the result as per the
pipelined methods.
Each intermediate operation is lazily executed and returns a stream as a result hence,
various intermediate operations can be pipelined. Terminal operations mark the end of
the stream and return the result.
1. Intermediate Operations
2. Terminal Operations
Intermediate Operations
Intermediate Operations are the types of operations in which multiple methods are chained in a
row.
It enables the concept of filtering where one method filters data and passes it to another
method after processing.
There are some benefits because of which we use Stream in Java as mentioned below:
No Storage
Pipeline of Functions
Laziness
Can be infinite
Can be parallelized
Can be created from collections, arrays, Files Lines, Methods in Stream, IntStream etc.
Syntax:
2. filter(): The filter method is used to select elements as per the Predicate passed as an
argument.
Syntax:
Syntax:
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
4. flatMap(): The flatMap operation in Java Streams is used to flatten a stream of collections
into a single stream of elements.
Syntax:
5. distinct(): Removes duplicate elements. It returns a stream consisting of the distinct elements
(according to Object.equals(Object)).
Syntax:
Stream<T> distinct()
6. peek(): Performs an action on each element without modifying the stream. It returns a
stream consisting of the elements of this stream, additionally performing the provided action on
each element as elements are consumed from the resulting stream.
Syntax:
Java program that demonstrates the use of all the intermediate operations:
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
Output
Intermediate Results:
STRUCTURE
STREAM
STATE
SORTING
Final Result:
SORTING
STATE
STREAM
STRUCTURE
Stream Operations:
filter(s -> s.startsWith("S")): Filters the strings to only include those that start with "S".
peek(...): Adds each processed element to the intermediateResults set for intermediate
inspection.
collect(Collectors.toList()): Collects the final processed strings into a list called result.
The program prints the intermediate results stored in the intermediateResults set. Finally, it
prints the result list, which contains the fully processed strings after all stream operations.
This example showcases how Java Streams can be used to process and manipulate collections of
data in a functional and declarative manner, applying transformations and filters in a sequence
of operations.
Terminal Operations
Terminal Operations are the type of Operations that return the result. These Operations are not
processed further just return a final result value.
1. collect(): The collect method is used to return the result of the intermediate operations
performed on the stream.
Syntax:
Syntax:
3. reduce(): The reduce method is used to reduce the elements of a stream to a single value.
The reduce method takes a BinaryOperator as a parameter.
Syntax:
Syntax:
long count()
Syntax:
Optional<T> findFirst()
Syntax:
Syntax:
Here ans variable is assigned 0 as the initial value and i is added to it.
Note: Intermediate Operations are running based on the concept of Lazy Evaluation, which
ensures that every method returns a fixed value(Terminal operation) before moving to the next
method.
import java.util.*;
import java.util.stream.Collectors;
Output:
List Creation:
Stream Operations:
collect: Filters names starting with 'S' and collects them into a new list.
reduce: Concatenates all names into a single string.
The program prints each name, names starting with 'S', concatenated names, the count of
names, the first name, whether all names start with 'S', and whether any name starts with 'S'.
Data Processing
Concurrent Processing
Important Points:
Stream is used to compute elements as per the pipelined methods without altering the
original value of the object.
In Java SE 9, Oracle Corp. has added four useful new methods to java.util.Stream interface. As
Stream is an interface, all those new implemented methods are default methods. It allows you
to create declarative pipelines of transformations on collections. There are four new methods
added to the Stream interface: dropWhile, takeWhile, ofNullable. The iterate method gets a
new overload, allowing you to provide a Predicate on when to stop iterating.
Java Comparable vs Comparator
In Java, both Comparable and Comparator interfaces are used for sorting objects. The
main difference between Comparable and Comparator is:
Comparable: It is used to define the natural ordering of the objects within the class.
The table below demonstrates the difference between comparable and comparator in Java.
It is implemented in a separate
It is implemented in the class.
Implementation class.
Example of Comparable
In this example, we use the Comparable interface to sort Movies by their release year using
compareTo() method.
// Constructor
public Movie(String name, double rating, int year) {
this.name = name;
this.rating = rating;
this.year = year;
}
// Getter methods
public String getName() {
return name;
}
Output
Movies after sorting by year:
Star Wars 8.7 1977
Empire Strikes Back 8.8 1980
Return of the Jedi 8.4 1983
Explanation: In the above example, the compareTo() method sorts the Movie objects by their
release year, where it return negative if the current movie year is earlier, return positive if the
current movie year is later and return zero if the year are same. The Collections.sort() method
uses the compareTo() method to compare and sort the movies in ascending order.
Now, suppose we want to sort movies by their rating and names as well. When we make a
collection element comparable (by having it implement Comparable), we get only one chance to
implement the compareTo() method. The solution is using Comparator.
Example of Comparator
In this example, we use Comparator interface to define custom sorting logic to sort movies
first by rating and then by name.
class Movie {
private String name;
private double rating;
private int year;
// Getter methods
public String getN() {
return name;
}
public double getR() {
return rating;
}
public int getY() {
return year;
}
}
// Main class
public class Geeks {
public static void main(String[] args) {
Output
Movies after sorting by year:
Star Wars 8.7 1977
Empire Strikes Back 8.8 1980
Return of the Jedi 8.4 1983
Explanation: In the above example, the compareTo() method sorts the Movie objects by their
release year, where it return negative if the current movie year is earlier, return positive if the
current movie year is later and return zero if the year are same. The Collections.sort() method
uses the compareTo() method to compare and sort the movies in ascending order.
Now, suppose we want to sort movies by their rating and names as well. When we make a
collection element comparable (by having it implement Comparable), we get only one chance to
implement the compareTo() method. The solution is using Comparator.
Example of Comparator
In this example, we use Comparator interface to define custom sorting logic to sort movies
first by rating and then by name.
class Movie {
private String name;
private double rating;
private int year;
// Getter methods
public String getN() {
return name;
}
public double getR() {
return rating;
}
public int getY() {
return year;
}
}
// Main class
public class Geeks {
public static void main(String[] args) {
Output
Movies sorted by rating:
8.8 Empire Strikes Back 1980
8.7 Star Wars 1977
8.3 Force Awakens 2015
Explanation: In the above example, the Comparator interface is used to sort the movies first by
rating and then by name. The Rating and NameCompare classes implement custom sorting
logic. The Collections.sort() method uses these comparators to sort the list by multiple criteria.
Important Point:
Comparator is a functional interface. So, now the question is why comparator is a functional
interface?
Answer: Java 8 introduced functional interface. These interfaces has only one abstract method.
But comparator has two methods that are compare(T o1, T o2) and equals(Object obj). Here,
only compare() is an abstract method. The equals() method is inherited from the Object
class and is not considered abstract in the interface.
Output:
To avoid abnormal termination, we use the Optional class. In the following example, we are
using Optional. So, our program can execute without crashing.
import java.util.Optional;
// Driver Class
public class OptionalDemo {
// Main Method
public static void main(String[] args)
{
String[] words = new String[10];
if (checkNull.isPresent()) {
String word = words[5].toLowerCase();
System.out.print(word);
}
else
System.out.println("word is null");
}
}
Output
word is null
Optional is a container object which may or may not contain a non-null value. You must
import java.util package to use this class. If a value is present, isPresent() will return true
and get() will return the value. Additional methods that depend on the presence or absence of a
contained value are provided, such as orElse() which returns a default value if the value is not
present, and ifPresent() which executes a block of code if the value is present. This is a value-
based class, i.e their instances are :
Static Methods: Static methods are the methods in Java that can be called without creating an
object of the class. They are referenced by the class name itself or reference to the object of
that class.
Syntax :
Important Points: Since Static methods belong to the class, they can be called to without
creating the object of the class. Below given are some important points regarding Static
Methods :
Static method(s) are associated with the class in which they reside i.e. they can be called
even without creating an instance of the class.
They are designed with the aim to be shared among all objects created from the same
class.
Static methods can not be overridden. But can be overloaded since they are resolved using
static binding by the compiler at compile time.
The following table shows the list of Static Methods provided by Optional Class :
Instance Methods: Instance methods are methods that require an object of its class to be
created before it can be called. To invoke an instance method, we have to create an Object of
the class within which it is defined.
Syntax :
Important Points: Instance Methods can be called within the same class in which they reside or
from the different classes defined either in the same package or other packages depending on
the access type provided to the desired instance method. Below given are some important
points regarding Instance Methods :
Instance method(s) belong to the Object of the class, not to the class i.e. they can be called
after creating the Object of the class.
Every individual object created from the class has its own copy of the instance method(s) of
that class.
They can be overridden since they are resolved using dynamic binding at run time.
The following table shows the list of Instance Methods provided by the Optional
Class :
Concrete Methods: A concrete method means, the method has a complete definition but can
be overridden in the inherited class. If we make this method final, then it can not be
overridden. Declaring method or class "final" means its implementation is complete. It is
compulsory to override abstract methods. Concrete Methods can be overridden in the
inherited classes if they are not final. The following table shows the list of Concrete Methods
provided by the Optional Class :
Below given are some examples :
Example 1 :
import java.util.Optional;
class GFG {
// Driver code
public static void main(String[] args)
{
Output
Optional.empty
Optional[Geeks Classes are coming soon]
Example 2 :
import java.util.Optional;
class GFG {
// Driver code
public static void main(String[] args)
{
Output
Geeks Classes are coming soon
1967487235
true
New Date-Time API in Java 8
New date-time API is introduced in Java 8 to overcome the following drawbacks of old date-time
API :
1. Not thread safe : Unlike old java.util.Date which is not thread safe the new date-time
API is immutable and doesn't have setter methods.
2. Less operations : In old API there are only few date operations but the new API provides
us with many date operations.
Java 8 under the package java.time introduced a new date-time API, most important classes
among them are :
LocalDate/LocalTime and LocalDateTime API : Use it when time zones are NOT required.
// Driver code
public static void main(String[] args)
{
LocalDateTimeApi();
}
}
Output
the current date is 2021-09-23
the current time is 20:52:39.954238
current date and time : 2021-09-23T20:52:39.956909
in formatted manner 23-09-2021 20:52:39
Month : SEPTEMBER day : 23 seconds : 39
the republic day :1950-01-26
specific date with current time : 2016-09-24T20:52:39.956909
Zoned date-time API : Use it when time zones are to be considered
ZonedDateTime tokyoZone =
currentZone.withZoneSameInstant(tokyo);
DateTimeFormatter format =
DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss");
// Driver code
public static void main(String[] args)
{
ZonedTimeAndDate();
}
}
Output:
formatted current Date and Time : 09-04-2018 06:21:13
the current zone is Etc/UTC
tokyo time zone is 2018-04-09T15:21:13.220+09:00[Asia/Tokyo]
formatted tokyo time zone 09-04-2018 15:21:13
LocalDate date2 =
LocalDate.of(2014, Month.DECEMBER, 12);
// Driver code
public static void main(String[] args)
{
checkingPeriod();
checkingDuration();
}
}
Output
gap between dates is a period of P6Y6M25D
the current time is 18:34:24.813548
after adding five hours of duration 23:34:24.813548
duration gap between time1 & time2 is PT-5H
// Driver code
public static void main(String[] args) {
checkingChronoEnum();
}
}
Output:
current date is :2018-04-09
next to next year is 2020-04-09
the next month is 2018-05-09
next week is 2018-04-16
20 years after today 2038-04-09
System.out.println("firstDayOfNextMonth : " +
dayOfNextMonth );
System.out.println("lastDayOfMonth : " +
lastDay);
}
// Driver code
public static void main(String[] args)
{
checkingAdjusters();
}
}
Output
the current date is 2021-07-09
firstDayOfNextMonth : 2021-08-01
next saturday from now is 2021-07-10
firstDayOfMonth : 2021-07-01
lastDayOfMonth : 2021-07-31
Before Java 8, interfaces could only have abstract methods. The implementation of these
methods has to be provided in a separate class. So, if a new method is to be added in an
interface, then its implementation code has to be provided in the class implementing the same
interface. To overcome this issue, Java 8 has introduced the concept of default methods which
allow the interfaces to have methods with implementation without affecting the classes that
implement the interface.
// default method
default void show()
{
System.out.println("Default Method Executed");
}
}
Output:
16
Default Method Executed
The default methods were introduced to provide backward compatibility so that existing
interfaces can use the lambda expressions without implementing the methods in the
implementation class. Default methods are also known as defender methods or virtual
extension methods.
Static Methods:
The interfaces can have static methods as well which is similar to static method of classes.
// static method
static void show()
{
System.out.println("Static Method Executed");
}
}
Output:
16
Static Method Executed
In case both the implemented interfaces contain default methods with same method signature,
the implementing class should explicitly specify which default method is to be used or it should
override the default method.
interface TestInterface2
{
// Default method
default void show()
{
System.out.println("Default TestInterface2");
}
}
Output:
Default TestInterface1
Default TestInterface2
Important Points:
Many a time, you want to create a collection (e.g., a List or Set) in your Java program and fill it
with some elements. That leads to repetitive coding where you instantiate the collection,
followed by several 'add' calls. With Java 9, several so-called collection factory methods have
been added. List and Set interfaces have “of()” methods to create an empty or no-empty
Immutable List or Set objects as shown below:
Map has two set of methods: of() methods and ofEntries() methods to create an Immutable
Map object and an Immutable Map.Entry object respectively.
CompletableFuture in Java
CompletableFuture provides a powerful and flexible way to write asynchronous, non-blocking
code. It was introduced in Java 8 and has become popular due to its ease of use and ability to
handle complex asynchronous workflows.
What is CompletableFuture?
Creating a CompletableFuture
Example:
import java.util.concurrent.*;
class GFG {
public static void main(String[] args) throws Exception
{
CompletableFuture<String> greetingFuture
= CompletableFuture.supplyAsync(() -> {
// some async computation
return "Hello from CompletableFuture";
});
System.out.println(greetingFuture.get());
}
}
Output:
Hello from CompletableFuture
This creates a CompletableFuture that will execute the lambda function passed
to supplyAsync in a separate thread. And after the execution, the result lambda function is
returned by CompletableFuture Object
Composing CompletableFuture
Example:
import java.util.concurrent.*;
class GFG {
public static void main(String[] args) throws Exception
{
CompletableFuture<String> helloFuture
= CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> greetingFuture
= CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combinedFuture
= helloFuture.thenCombine(
greetingFuture, (m1, m2) -> m1 + " " + m2);
System.out.println(combinedFuture.get());
}
}
Output:
Hello World
This creates two instances of CompletableFuture that return "hello" and "world". And
using thenCombine, the result of both the CompletableFutures are concatenated and returned
as a final result.
Handling Multiple CompletableFutures
Sometimes we need to run multiple independent CompletableFuture tasks in parallel and wait
for all of them to complete, so in this case we can use methods like allOf and anyOf.
Example:
import java.util.concurrent.*;
class GFG {
public static void main(String[] args) throws Exception {
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> 20);
Output
Future1 Result: 10
Future2 Result: 20
Note: The allOf() method is used to combine the results of multiple CompletableFuture
instances.
CompletableFuture provides methods like exceptionally and handle to handle exceptions and
errors that might happen during asynchronous computation and provide a fallback value or
perform some alternative operation.
Example:
import java.util.concurrent.*;
class GFG {
public static void main(String[] args) throws Exception
{
CompletableFuture<Integer> resultFuture
// java.lang.ArithmeticException: / by zero
= CompletableFuture.supplyAsync(() -> 10 / 0)
.exceptionally(ex -> 0);
Output:
CompletableFuture API can be used with "java.util.concurrent" package in Java. It helps to work
with Asynchronous computations. Some of the features that were added to this API in Java 9 are
mentioned below:
Reactive Streams
Reactive Streams API is used for efficiently handling the Asynchronous Data Streams. It uses
interfaces and class for implementing the reactive streams. Components of Reactive Streams
API in Java are mentioned below:
In many applications, data is not retrieved from a fixed storage device, but rather, handled in
near-real-time, with users or other systems rapidly injecting information into our system. Most
times this data injection is asynchronous, where we do not know ahead of time when the data
will be present. In order to facilitate this asynchronous style of data handling, we have to
rethink older polling-based models and instead, use a lighter, more streamlined method.
This is where reactive streams come into their own. Instead of having a client and server style of
data handling, where a client requests data from a server and the server responds, possibly
asynchronously, to the client with the requested data, we instead use a publish-subscribe
mechanism: A subscriber informs a publisher that it is willing to accept a given number
of items (requests a given number of items), and if items are available, the publisher pushes the
maximum receivable number of items to the subscriber. It is important to note that this is a
two-way communication, where the subscriber informs the publisher how many items it is
willing to handle and the publisher pushes that number of items to the subscriber.
The process of restricting the number of items that a subscriber is willing to accept (as judged
by the subscriber itself) is called backpressure and is essential in prohibiting the overloading of
the subscriber (pushing more items that the subscriber can handle). This scheme is illustrated in
the figure below.
This two-way connection between a publisher and a subscriber is called a subscription. This
subscription binds a single publisher to a single subscriber (one-to-one relationship) and may be
unicast or multicast. Likewise, a single publisher may have multiple subscribers subscribed to it,
but a single subscriber may only be subscribed to a single producer (a publisher may have many
subscribers, but a subscriber may subscribe to at most one publisher).
When a subscriber subscribes to a publisher, the publisher notifies the subscriber of the
subscription that was created, allowing the subscriber to store a reference to the subscription (if
desired). Once this notification process is completed, the subscriber can inform the publisher
that it is ready to receive some n number of items.
When the publisher has items available, it then sends at most n number of items to the
subscriber. If an error occurs in the publisher, it signals the subscriber of the error. If the
publisher is permanently finished sending data, it signals the subscriber that it is complete. If
the subscriber is notified that either an error occurred or the publisher is complete, the
subscription is considered canceled and no more interactions between the publisher and
subscriber (or the subscription) will take place. This subscription workflow is illustrated in the
figure below.
Processors
Interface Representation
Given the description above, Reactive Streams are made up of four main entities: (1) publishers,
(2) subscribers, (3) subscriptions, and (4) processors. From an interface perspective, publishers
are only required to allow subscribers to subscribe to them. Therefore, we can create a simple
interface for a publisher, where the formal generic type parameter, T, represents the type of the
items that the publisher produces:
This interface definition requires that we subsequently define the interface for a subscriber. As
stated above, a subscriber has four main interactions: (1) notification of being subscribed, (2)
accepting pushed items, (3) accepting errors that occur in a subscribed publisher, and (4)
notification when a publisher is complete. This results in the following interface, which is
likewise parameterized by the type of the items it requests:
Next, we must define the interface for a subscription. This entity is simpler than a subscriber
and responsible for only two actions: (1) accepting requests for items and (2) being canceled.
This results in the following interface definition:
While these four interfaces constitute the codified contract for Reactive Streams, there are a
number of other restrictions and intended behaviors that these interfaces must conform to.
These specifications, along with the above interface definitions, can be found in the Reactive
Streams JVM Specification. As we will see in the next section, the standard Java implementation
of the Reactive Stream specification is nearly identical to that of the Reactive Streams JVM
specification and acts as a standardization of the Reactive Streams contracts within the Java
Standard Library.
@FunctionalInterface
public static interface Publisher<T> {
public void subscribe(Subscriber<? super T> subscriber);
}
While there is not much new to discuss when comparing the Reactive Streams JVM specification
to the standard Java definitions, the standard Java version does include one publisher
implementation: SubmissionPublisher. The SubmissionPublisher class acts as a simple publisher,
which accepts items to push to subscribers using a submit(T item) method. When an item is
submitted to the submit method, it is asynchronously pushed to subscribers, as in the following
example:
@Override
public void onSubscribe(Subscription subscription) {
this.subscription = subscription;
subscription.request(1);
}
@Override
public void onNext(Integer item) {
System.out.println("Received item: " + item);
subscription.request(1);
}
@Override
public void onError(Throwable error) {
System.out.println("Error occurred: " + error.getMessage());
}
@Override
public void onComplete() {
System.out.println("PrintSubscriber is complete");
}
}
System.out.println("Submitting items...");
Thread.sleep(1000);
publisher.close();
}
}
Submitting items...
Received item: 0
Received item: 1
Received item: 2
Received item: 3
Received item: 4
Received item: 5
Received item: 6
Received item: 7
Received item: 8
Received item: 9
PrintSubscriber is complete
Within our subscriber, we capture the Subscription object that was passed to
the onSubscribe method, allowing us to interact with the Subscription at a later time. Once we
store the Subscription object, we immediately inform the Subscription that our subscriber is
ready to accept one item (by calling subscription.request(1)). We do likewise within
the onNext method after we print the received item. This amounts to informing the publisher
that we are ready to accept another item as soon as we have finished processing an item.
In our main method, we simply instantiate a SubmissionPublisher and our PrintSubscriber and
subscribe the latter to the former. Once the subscription is established, we submit the
values 0 through 9 to the publisher, which in turn asynchronously pushes the values to the
subscriber. The subscriber then handles each item by printing its value to standard output and
informs the subscription that it is ready to accept another value. We then pause the main
thread for 1 second to allow for the asynchronous submissions to complete. This is a very
important step since the submit method asynchronously pushes the submitted items to its
subscribers. Therefore, we must provide a reasonable period of time for the asynchronous
action to complete. Lastly, we close the publisher, which in turn notifies our subscriber that the
subscription has completed.
We can also introduce a processor and chain the original publisher and subscriber with this
processor. In the following example, we create a processor that increments received values by
10 and pushes the incremented values to its subscriber:
@Override
public void onSubscribe(Subscription subscription) {
this.subscription = subscription;
subscription.request(1);
}
@Override
public void onNext(Integer item) {
submit(item + 10);
subscription.request(1);
}
@Override
public void onError(Throwable error) {
error.printStackTrace();
closeExceptionally(error);
}
@Override
public void onComplete() {
System.out.println("PlusTenProcessor completed");
close();
}
}
System.out.println("Submitting items...");
Thread.sleep(1000);
publisher.close();
}
}
Submitting items...
Received item: 10
Received item: 11
Received item: 12
Received item: 13
Received item: 14
Received item: 15
Received item: 16
Received item: 17
Received item: 18
Received item: 19
PlusTenProcessor completed
PrintSubscriber is complete
As we expected, each of the pushed values is incremented by 10 and the events received by the
processor (such as receiving an error or completing) are forwarded to the subscriber, resulting
in a completed message printed for both the PlusTenProcessor and the PrintSubscriber.
Conclusion
In the age of near-real-time data processing, Reactive Streams are a de facto standard. The
ubiquity of this programming style has led to numerous implementations of this standard, each
with its own duplicate set of interfaces. In an effort to collect these common interfaces into a
universal standard for Java, JDK 9 now includes Reactive Streams interfaces, along with a
powerful publisher implementation, by default. As we have seen in this article, while these
interfaces are unassuming in appearance, they provide a rich method for handling streaming
data flows in a standard, interchangeable manner.
Java Modules
Java Module System provides an additional layer of encapsulation to our programs as we can
specify which package can be utilized by the modules, and which modules could have entry to
to them. Before entering into the exclusive types of modules in Java, we will first learn the
differences among applications and modules. We recognize that there are numerous
applications in Java including IO bundle, AWT package, swing bundle, and so on.
The principal difference between the module and package is given below.
1. Module: In easy words, we can say that the module is a set of related applications or It is a
collection of related applications such that it affords an API handy to different modules that are
internal and encapsulated.
Suppose permits take an example from the built-in module in Java let's take java.util for
example. If we expect java.util as a module we recognize that there are a variety of instructions
and sub-packages inside the java.util. Now we've assumed that java.util is a package deal the
modules could be like java.util.Collections and java.util.stream.
2. Package: A package is a set of classes, interfaces, and sub-packages that are similar. There are
mainly two types of packages in Java, they are:
User Defined packages: The packages that contain the classes or interfaces which are built
based on the user and it is nothing but they are just defined by the user.
Built-In Packages: The packages that come pre-installed when we configure the Java in our
system are called the built-in packages. For example, as we specified earlier such as
java.net, java.awt, javax.swing, java.sql etc
Java 9 has one of the major changes in its features which is the Java module System. The main
aim of the system is to collect Java packages and code to be collected into a single unit called a
Module. The reason to add this feature in Java is that when we want to create modular
applications using Java the earlier versions before 9 have no system like this that's why the size
of the application has increased. Even the JDK file in the earlier version of Java has a large size
only the rt.jar file size was around 64 MB.
To avoid this situation Java 9 has split the JDK into a set of modules. so that we can use the
required module to develop our application. Apart from this, the Java 9 version has also
provided a feature so that the user could create their own module and develop their own
applications.
This version of Java includes various options for Java tools such as javac and java etc. In
which we can specify the module path and help us to locate the location of the module
in the Java 9.
The JAR [ java Archive] file was introduced in this version of java. The JAR contains module-
info.class file in the folder. [ JAR is a file format which is a zip file which is used for
aggregation of many files into one ]
As we specified earlier the earlier versions of java has no modular system and the size
of rt.jar file was large so the JDK and JRE was split into modules so that we can use the
modules we want to develop our application.
Another thing is that the java 9 has introduced a new URL method for naming the class and
modules.
JMOD file format is also introduced which is used to handle other configuration files in java.
Java 9 Module
The collection of packages and code into a single unit is called module . The module can be
described with the help of module descriptor which is named by module-info.java and the
module declarator describes the below three that were shown in the image.
The next point is that it should contain what does the module contains and what does the
module export in the java
Earlier we supposed that our java module name is org.geeksforgeeks which has followed the
reverse domain pattern to overcome the naming conflicts.
Creating the java modules involves three steps to be created in java. The three steps to be
followed are:
We have to create a directory structure mentioned below . In order to create a module we have
to follow the reverse domain pattern in a similiar way we create packages in java.
Create a file name module-info.java as module declarator and in the interior of the file create a
module using the module identifier . After the module identifier use the module name same as
directory name and if the module-info.java has no dependency leave it empty and save it in the
as mentioned below:
In the src/org.geeksforgeeks save the file as module-info.java as mentioned in the above image.
module org.geeksforgeeks {
//empty body
}
Now we can create the source code file with the name Main.java and save it and save the file in
the src/org.geeksforgeeks/org/geeksforgeeks as mentioned and shown in the above image.
Output
Hello welcome geek and know about java 9 module system
To compile the java module, run the below command so that we will get the directory structure
as mentioned below.
It will show and create the following directory structure after compiling the above command
and after executing the module-info.class and Main.class are generated and which will be under
the module_name that we specified in the above command after -d which keeps in the
destination and it is shown in the below structure.
Run the source code in the module
Use the below command in the java to run the module and to get the Main.java source code
that is present in the module.
Now if we want to see the module descriptor file, we can run the following command and see
the output.
Output
module org.geeksforgeeks {
requires java.base;
}