0% found this document useful (0 votes)
5 views79 pages

Java 8 Features

The document outlines new features in Java, particularly focusing on lambda expressions and functional interfaces introduced in Java 8. It explains how lambda expressions provide a concise way to implement functional interfaces, along with examples and syntax for different types of lambda expressions. Additionally, it discusses the concept of functional interfaces, their annotations, and various built-in functional interfaces available in Java.

Uploaded by

gvrbabu1946
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views79 pages

Java 8 Features

The document outlines new features in Java, particularly focusing on lambda expressions and functional interfaces introduced in Java 8. It explains how lambda expressions provide a concise way to implement functional interfaces, along with examples and syntax for different types of lambda expressions. Additionally, it discusses the concept of functional interfaces, their annotations, and various built-in functional interfaces available in Java.

Uploaded by

gvrbabu1946
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 79

Java New 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 in Java, introduced in Java SE 8. It represents the instances of functional


interfaces (interfaces with a single abstract method). They provide a concise way to express
instances of single-method interfaces using a block of code.

Key Functionalities of Lambda Expression

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.

 Code as Data: Treat functionality as a method argument.

 Class Independence: Create functions without defining a class.

 Pass and Execute: Pass lambda expressions as objects and execute on demand.

Example: Implementing a Functional Interface with Lambda


The below Java program demonstrates how a lambda expression can be used to implement a
user-defined functional interface.

// Java program to demonstrate lambda expressions


// to implement a user defined functional interface.

// A sample functional interface (An interface with


// single abstract method
interface FuncInterface
{
// An abstract function
void abstractFun(int x);

// A non-abstract (or default) function


default void normalFun()
{
System.out.println("Hello");
}
}

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

// This calls above lambda expression and prints 10.


fobj.abstractFun(5);
}
}

Output
10

Structure of Lambda Expression

Below diagram demonstrates the structure of Lambda Expression:


Syntax of Lambda Expressions

Java Lambda Expression has the following syntax:

(argument list) -> { body of the expression }

Components:

 Argument List: Parameters for the lambda expression

 Arrow Token (->): Separates the parameter list and the body

 Body: Logic to be executed.

Types of Lambda Parameters

There are three Lambda Expression Parameters are mentioned below:

1. Lambda with Zero Parameter

2. Lambda with Single Parameter

3. Lambda with Multiple Parameters

1. Lambda with Zero Parameter

Syntax:

() -> System.out.println("Zero parameter lambda");

Example: The below Java program demonstrates a Lambda expression with zero parameter.

// Java program to demonstrates Lambda expression with zero parameter


@FunctionalInterface
interface ZeroParameter {
void display();
}

public class Geeks {


public static void main(String[] args)
{
// Lambda expression with zero parameters
ZeroParameter zeroParamLambda = ()
-> System.out.println(
"This is a zero-parameter lambda expression!");

// Invoke the method


zeroParamLambda.display();
}
}
Output
This is a zero-parameter lambda expression!

2. Lambda with a Single Parameter

Syntax:

(p) -> System.out.println("One parameter: " + p);

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.

// Java program to demonstrate simple lambda expressions


import java.util.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));

// Using lambda expression to print even elements


// of al
System.out.println(
"Even elements of the ArrayList: ");
al.forEach(n -> {
if (n % 2 == 0)
System.out.println(n);
});
}
}
Output
Elements of the ArrayList:
1
2
3
4
Even elements of the ArrayList:
2
4

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.

3. Lambda Expression with Multiple Parameters

Syntax:

(p1, p2) -> System.out.println("Multiple parameters: " + p1 + ", " + p2);

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

public class Test {

public static void main(String[] args) {

// Using lambda expressions to define the operations


Functional add = (a, b) -> a + b;
Functional multiply = (a, b) -> a * b;

// Using the operations


System.out.println(add.operation(6, 3));
System.out.println(multiply.operation(4, 5));
}
}
Output
9
20

Note: Lambda expressions are just like functions and they accept parameters just like functions.

Common Built-in Functional Interfaces

 Comparable<T>: int compareTo(T o);

 Comparator<T>: int compare(T o1, T o2);

These are commonly used in sorting and comparisons.

Note: Other commonly used interface include Predicate<T>, it is used to test


conditions, Function<T, R>, it represent a function that take the argument of type T and return a
result of type R and Supplier<T>, it represent a function that supplies results.

1. () -> {};
2. () -> "geeksforgeeks";
3. () -> { return "geeksforgeeks";};
4. (Integer i) -> {return "geeksforgeeks" + i;}
5. (String s) -> {return "geeksforgeeks";};

4 and 5 are invalid lambdas, the rest are valid. Details:

1. This lambda has no parameters and returns void. It’s similar to a method with an empty
body: public void run() { }.

2. This lambda has no parameters and returns a String as an expression.


3. This lambda has no parameters and returns a String (using an explicit return statement,
within a block).

4. return is a control-flow statement. To make this lambda valid, curly braces are required
as follows:

(Integer i) -> {return "geeksforgeeks" + i;}

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:

(String s) -> {return "geeks for geeks";}

Java Functional Interfaces


A functional interface in Java is an interface that contains only one abstract method. Functional
interfaces can have multiple default or static methods, but only one abstract
method. Runnable, ActionListener, and Comparator are common examples of Java functional
interfaces. From Java 8 onwards, lambda expressions and method references can be used to
represent the instance of a functional interface.

Example: Using a Functional Interface with Lambda Expression

public class Geeks {

public static void main(String[] args) {

// Using lambda expression


// to implement Runnable
new Thread(() -> System.out.println("New thread created")).start();
}
}

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.

Functional Interface is additionally recognized as Single Abstract Method Interfaces. In short,


they are also known as SAM interfaces. Functional interfaces in Java are a new feature that
provides users with the approach of fundamental programming.

Note: A functional interface can also extend another functional interface.

The @FunctionalInterface annotation can be used to indicate that an interface is intended to be a


functional interface. If an interface has more than one abstract method, it cannot be a functional
interface.

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

Note: @FunctionalInterface annotation is optional but it is a good practice to use. It helps


catching the error in early stage by making sure that the interface has only one abstract method.

Example: Defining a Functional Interface with @FunctionalInterface Annotation

// Define a functional interface


@FunctionalInterface

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;

// parameter passed and return type must be


// same as defined in the prototype
int ans = s.calculate(a);
System.out.println(ans);
}
}
Output
25

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.

Java Functional Interfaces Before Java 8

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:

// Java program to demonstrate functional interface


// before Java 8
class Geeks {
public static void main(String args[]) {

// create anonymous inner class object


new Thread(new Runnable() {
@Override public void run()
{
System.out.println("New thread created");
}
}).start();
}
}

Output
New thread created

Built-In Java Functional Interfaces

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:

 Runnable: This interface only contains the run() method.

 Comparable: This interface only contains the compareTo() method.

 ActionListener: This interface only contains the actionPerformed() method.

 Callable: This interface only contains the call() method.

Types of Functional Interfaces in Java


Java SE 8 included four main kinds of functional interfaces which can be applied in multiple
situations as mentioned below:

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.

Syntax / Prototype of Consumer Functional Interface:

Consumer<Integer> consumer = (value) -> System.out.println(value);

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

The Predicate interface represents a boolean-valued function of one argument. It is commonly


used for filtering operations in streams.

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:

public interface Predicate<T> {


boolean test(T t);
}
The Java predicate functional interface can also be implemented using Lambda expressions.

Predicate predicate = (value) -> value != null;

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:

Function<Integer, Integer> function = (value) -> value * value;

 Bi-Function: The Bi-Function is substantially related to a Function. Besides, it takes two


arguments, whereas Function accepts one argument.

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

Supplier<String> supplier = () -> "Hello, World!";

Functional Interfaces Table

Functional Interfaces Description Method

Runnable It represents a task that can be void run()


executed by a thread.
Functional Interfaces Description Method

Comparable It compares two objects for ordering. int compareTo(T o)

ActionListener It handles an action event in event- void actionPerformed(ActionEvent e)


driven programming.

Callable It represents a task that can return a V call() throws Exception


result or throw an exception.

Consumer It accepts a single input argument and void accept(T t)


returns no result.

Predicate It accepts a single argument and boolean test(T t)


returns a boolean result.

Function It accepts a single argument and R apply(T t)


returns a result.

Supplier It does not take any arguments but T get()


provides a result.

BiConsumer It accepts two arguments and returns void accept(T t, U u)


no result.

BiPredicate It accepts two arguments and returns a boolean test(T t, U u)


boolean result.

BiFunction It accepts two arguments and returns a R apply(T t, U u)


Functional Interfaces Description Method

result.

UnaryOperator This is a special case of Function, where T apply(T t)


input and output types are the same.

BinaryOperator This is a special case of BiFunction, T apply(T t1, T t2)


where input and output types are the
same.

Example: Using Predicate Interface to Filter Strings

// Demonstrate Predicate Interface


import java.util.*;
import java.util.function.Predicate;

class Geeks {
public static void main(String args[]) {

// create a list of strings


List<String> n = Arrays.asList(
"Geek", "GeeksQuiz", "g1", "QA", "Geek2");

// declare the predicate type as string and use


// lambda expression to create object
Predicate<String> p = (s) -> s.startsWith("G");

// Iterate through the list


for (String st : n) {

// call the test method


if (p.test(st))
System.out.println(st);
}
}
}

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.

 The java.util.function package contains many built-in functional interfaces in Java 8.

Java Method References


In Java, a method is a collection of statements that perform some specific task and return the
result to the caller. A method reference is the shorthand syntax for a lambda expression that
contains just one method call. In general, one does not have to pass arguments to method
references.

Why Use Method References?

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.

 Reusability increases because we can directly use the existing methods.

Note: Functional Interfaces in Java and Lambda Function are prerequisites required in order to
grasp a grip over Method References in Java.

Example:

// Using Method Reference


import java.util.Arrays;
public class Geeks
{
// Method
public static void print(String s) {
System.out.println(s);
}

public static void main(String[] args)


{
String[] names = {"Geek1", "Geek2", "Geek3"};

// Using method reference to print each name


Arrays.stream(names).forEach(Geeks::print);
}
}

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.

Key Benefits of Method References

The key benefits of method references are listed below:

 Improved Readability: Method references simplify the code by removing boilerplate


syntax.

 Reusability: Existing methods can be directly reused, enhancing modularity.


 Functional Programming Support: They work seamlessly with functional interfaces and
lambdas.

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.

// This square function is a variable getSquare.


Function<Integer, Integer> getSquare = i -> i * i ;
// Pass function as an argument to another function easily
SomeFunction(a, b, getSquare) ;

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.

Generic Syntax for Method References

Aspect Syntax

Object :: methodName
Refer to a method in an object

list.forEach(s -> System.out.println(s));


Print all elements in a list

Shorthand to print all elements in a


list list.forEach(System.out::println);
Aspect Syntax

Types of Method References

There are four type method references that are as follows:

1. Static Method Reference

2. Instance Method Reference of a particular object

3. Instance Method Reference of an arbitrary object of a particular type

4. Constructor Reference

To look into all these types we will consider a common example of sorting with a comparator
which is as follows:

1. Reference to a Static Method

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:

// Reference to a static method


import java.io.*;
import java.util.*;
class Person
{
private String name;
private Integer age;

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

// Static method to compare with age


public static int compareByAge(Person a, Person b) {
return a.getAge().compareTo(b.getAge());
}

// Main driver method


public static void main(String[] args) {

// Creating an empty ArrayList of user-defined type


// List of person
List<Person> personList = new ArrayList<>();

// Adding elements to above List


// using add() method
personList.add(new Person("Vicky", 24));
personList.add(new Person("Poonam", 25));
personList.add(new Person("Sachin", 19));

// Using static method reference to


// sort array by name
Collections.sort(personList, Geeks::compareByName);
// Display message only
System.out.println("Sort by Name :");

// Using streams over above object of Person type


personList.stream()
.map(x -> x.getName())
.forEach(System.out::println);

System.out.println();

// Now using static method reference


// to sort array by age
Collections.sort(personList, Geeks::compareByAge);

// Display message only


System.out.println("Sort by Age :");

// Using streams over above object of Person type


personList.stream()
.map(x -> x.getName())
.forEach(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.

2. Reference to an Instance Method of a Particular Object


This type of method means using a method from a certain object which we already have. We do
not need to write another function to call that particular method we can just simply refer to it
directly.

Syntax:

// Lambda expression
(args) -> obj.instanceMethod(args);
// Method reference
obj::instanceMethod;

Example:

// Reference to an Instance Method of


// a Particular Object
import java.io.*;
import java.util.*;

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

// To compare with age


public int compareByAge(Person a, Person b) {
return a.getAge().compareTo(b.getAge());
}
}

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

// Adding elements to above object


// using add() method
personList.add(new Person("Vicky", 24));
personList.add(new Person("Poonam", 25));
personList.add(new Person("Sachin", 19));

// A comparator class with multiple


// comparator methods
ComparisonProvider comparator
= new ComparisonProvider();

// Now using instance method reference


// to sort array by name
Collections.sort(personList, comparator::compareByName);

// Display message only


System.out.println("Sort by Name :");

// Using streams
personList.stream()
.map(x -> x.getName())
.forEach(System.out::println);

System.out.println();

// Using instance method reference


// to sort array by age
Collections.sort(personList, comparator::compareByAge);

// Display message only


System.out.println("Sort by Age :");
personList.stream()
.map(x -> x.getName())
.forEach(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.

3. Reference to an Instance Method of an Arbitrary Object of a Particular Type

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:

// Reference to an Instance Method of an


// Arbitrary Object of a Particular Type
import java.io.*;
import java.util.*;
public class Geeks
{
public static void main(String[] args)
{
// Creating an empty ArrayList of user defined type
// List of person
List<String> personList = new ArrayList<>();

// Adding elements to above object of List


// using add() method
personList.add("Vicky");
personList.add("Poonam");
personList.add("Sachin");

// Method reference to String type


Collections.sort(personList, String::compareToIgnoreCase);

// Printing the elements(names) on console


personList.forEach(System.out::println);
}
}

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

4. Constructor Method Reference

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:

// Java Program to Illustrate How We can Use


// constructor method reference

// Importing required classes


import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.function.*;

// Object need to be sorted


class Person {
private String name;
private Integer age;

// Constructor
public Person()
{
Random ran = new Random();

// Assigning a random value


// to name
this.name
= ran
.ints(97, 122 + 1)
.limit(7)
.collect(StringBuilder::new,
StringBuilder::appendCodePoint,
StringBuilder::append)
.toString();
}

public Integer getAge()


{
return age;
}
public String getName()
{
return name;
}
}

public class Geeks {


// Get List of objects of given
// length and Supplier
public static <T> List<T>
getObjectList(int length,
Supplier<T> objectSupply)
{
List<T> list = new ArrayList<>();

for (int i = 0; i < length; i++)


list.add(objectSupply.get());
return list;
}

public static void main(String[] args)


{

// Get 10 person by supplying


// person supplier, Supplier is
// created by person constructor
// reference
List<Person> personList
= getObjectList(5, Person::new);

// Print names of personList


personList.stream()
.map(x -> x.getName())
.forEach(System.out::println);
}
}

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.

Common Use Cases


There are some common cases where we use Method References in Java as mentioned below:

 Iterating over collections: Simplifying operations like printing or processing elements.

 Stream API operations: Enhancing readability in filtering, mapping, and reducing


operations.

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

Use of Stream in Java

The uses of Stream in Java are mentioned below:

 Stream API is a way to express and process collections of objects.

 Enable us to perform operations like filtering, mapping, reducing, and sorting.

How to Create a Java Stream?

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.

Java Stream Features

The features of Java streams are mentioned below:

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

Different Operations on Streams

There are two types of Operations in Streams:

1. Intermediate Operations

2. Terminal Operations
Intermediate Operations

Intermediate Operations are the types of operations in which multiple methods are chained in a
row.

Characteristics of Intermediate Operations

 Methods are chained together.

 Intermediate operations transform a stream into another stream.

 It enables the concept of filtering where one method filters data and passes it to another
method after processing.

Benefit of Java Stream

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.

Important Intermediate Operations

There are a few Intermediate Operations mentioned below:


1. map(): The map method is used to return a stream consisting of the results of applying the
given function to the elements of this stream.

Syntax:

<R> Stream<R> map(Function<? super T, ? extends R> mapper)

2. filter(): The filter method is used to select elements as per the Predicate passed as an
argument.

Syntax:

Stream<T> filter(Predicate<? super T> predicate)

3. sorted(): The sorted method is used to sort the stream.

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:

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)

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:

Stream<T> peek(Consumer<? super T> action)

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;

public class StreamIntermediateOperationsExample {


public static void main(String[] args) {
// List of lists of names
List<List<String>> listOfLists = Arrays.asList(
Arrays.asList("Reflection", "Collection", "Stream"),
Arrays.asList("Structure", "State", "Flow"),
Arrays.asList("Sorting", "Mapping", "Reduction", "Stream")
);

// Create a set to hold intermediate results


Set<String> intermediateResults = new HashSet<>();

// Stream pipeline demonstrating various intermediate operations


List<String> result = listOfLists.stream()
.flatMap(List::stream) // Flatten the list of lists into a single stream
.filter(s -> s.startsWith("S")) // Filter elements starting with "S"
.map(String::toUpperCase) // Transform each element to uppercase
.distinct() // Remove duplicate elements
.sorted() // Sort elements
.peek(s -> intermediateResults.add(s)) // Perform an action (add to set) on each element
.collect(Collectors.toList()); // Collect the final result into a list

// Print the intermediate results


System.out.println("Intermediate Results:");
intermediateResults.forEach(System.out::println);

// Print the final result


System.out.println("Final Result:");
result.forEach(System.out::println);
}
}

Output
Intermediate Results:
STRUCTURE
STREAM
STATE
SORTING
Final Result:
SORTING
STATE
STREAM
STRUCTURE

Explanation of the above Program:

List of Lists Creation:

 The listOfLists is created as a list containing other lists of strings.

Stream Operations:

 flatMap(List::stream): Flattens the nested lists into a single stream of strings.

 filter(s -> s.startsWith("S")): Filters the strings to only include those that start with "S".

 map(String::toUpperCase): Converts each string in the stream to uppercase.

 distinct(): Removes any duplicate strings.

 sorted(): Sorts the resulting strings alphabetically.

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

Important Terminal Operations

There are a few Terminal Operations mentioned below:

1. collect(): The collect method is used to return the result of the intermediate operations
performed on the stream.

Syntax:

<R, A> R collect(Collector<? super T, A, R> collector)


2. forEach(): The forEach method is used to iterate through every element of the stream.

Syntax:

void forEach(Consumer<? super T> action)

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:

T reduce(T identity, BinaryOperator<T> accumulator)


Optional<T> reduce(BinaryOperator<T> accumulator)

4. count(): Returns the count of elements in the stream.

Syntax:

long count()

5. findFirst(): Returns the first element of the stream, if present.

Syntax:

Optional<T> findFirst()

6. allMatch(): Checks if all elements of the stream match a given predicate.

Syntax:

boolean allMatch(Predicate<? super T> predicate)

7. anyMatch(): Checks if any element of the stream matches a given predicate.

Syntax:

boolean anyMatch(Predicate<? super T> predicate)

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.

Java Program Using all Terminal Operations:

import java.util.*;
import java.util.stream.Collectors;

public class StreamTerminalOperationsExample {


public static void main(String[] args) {
// Sample data
List<String> names = Arrays.asList(
"Reflection", "Collection", "Stream",
"Structure", "Sorting", "State"
);

// forEach: Print each name


System.out.println("forEach:");
names.stream().forEach(System.out::println);

// collect: Collect names starting with 'S' into a list


List<String> sNames = names.stream()
.filter(name -> name.startsWith("S"))
.collect(Collectors.toList());
System.out.println("\ncollect (names starting with 'S'):");
sNames.forEach(System.out::println);

// reduce: Concatenate all names into a single string


String concatenatedNames = names.stream().reduce(
"",
(partialString, element) -> partialString + " " + element
);
System.out.println("\nreduce (concatenated names):");
System.out.println(concatenatedNames.trim());

// count: Count the number of names


long count = names.stream().count();
System.out.println("\ncount:");
System.out.println(count);

// findFirst: Find the first name


Optional<String> firstName = names.stream().findFirst();
System.out.println("\nfindFirst:");
firstName.ifPresent(System.out::println);

// allMatch: Check if all names start with 'S'


boolean allStartWithS = names.stream().allMatch(
name -> name.startsWith("S")
);
System.out.println("\nallMatch (all start with 'S'):");
System.out.println(allStartWithS);

// anyMatch: Check if any name starts with 'S'


boolean anyStartWithS = names.stream().anyMatch(
name -> name.startsWith("S")
);
System.out.println("\nanyMatch (any start with 'S'):");
System.out.println(anyStartWithS);
}
}

Output:

Explanation of the above Program:

List Creation:

 The names list is created with sample strings.

Stream Operations:

 forEach: Prints each name in the list.

 collect: Filters names starting with 'S' and collects them into a new list.
 reduce: Concatenates all names into a single string.

 count: Counts the total number of names.

 findFirst: Finds and prints the first name in the list.

 allMatch: Checks if all names start with 'S'.

 anyMatch: Checks if any name starts with 'S'.

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

Real-World Use Cases of Java Streams

Streams are widely used in modern Java applications for:

 Data Processing

 For processing JSON/XML responses

 For database Operations

 Concurrent Processing

Important Points:

 A stream consists of a source followed by zero or more intermediate methods combined


together (pipelined) and a terminal method to process the objects obtained from the
source as per the methods described.

 Stream is used to compute elements as per the pipelined methods without altering the
original value of the object.

Stream API Improvements

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.

 Comparator: It is used to define custom sorting logic externally.

Difference Between Comparable and Comparator

The table below demonstrates the difference between comparable and comparator in Java.

Features Comparable Comparator

It defines natural ordering within It defines external or custom


Definition the class. sorting logic.

Method compareTo() compare()

It is implemented in a separate
It is implemented in the class.
Implementation class.

Sorting Criteria Natural order sorting Custom order sorting

It is used for a single sorting It is used for multiple sorting


Usage order. orders.

Example of Comparable

In this example, we use the Comparable interface to sort Movies by their release year using
compareTo() method.

// Java program to demonstrate Comparable interface


import java.util.ArrayList;
import java.util.Collections;
// Movie class implements Comparable
// interface to define default sorting
class Movie implements Comparable<Movie> {
private String name;
private double rating;
private int year;

// Constructor
public Movie(String name, double rating, int year) {
this.name = name;
this.rating = rating;
this.year = year;
}

// Implementation of the compareTo method


// for default sorting by year
public int compareTo(Movie m) {

// Sort movies in ascending


// order of year
return this.year - m.year;
}

// Getter methods
public String getName() {
return name;
}

public double getRating() {


return rating;
}

public int getYear() {


return year;
}
}

public class Geeks {


public static void main(String[] args) {

// Create a list of movies


ArrayList<Movie> l = new ArrayList<>();
l.add(new Movie("Star Wars", 8.7, 1977));
l.add(new Movie("Empire Strikes Back", 8.8, 1980));
l.add(new Movie("Return of the Jedi", 8.4, 1983));

// Sort movies using Comparable's


// compareTo method by year
Collections.sort(l);

// Display the sorted list of movies


System.out.println("Movies after sorting by year:");
for (Movie m : l) {
System.out.println(m.getName() + " " + m.getRating() + " " + m.getYear());
}
}
}

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.

// Java program to demonstrate Comparator interface


import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

class Movie {
private String name;
private double rating;
private int year;

// Constructor to initialize movie details


public Movie(String name, double rating, int year) {
this.name = name;
this.rating = rating;
this.year = year;
}

// Getter methods
public String getN() {
return name;
}
public double getR() {
return rating;
}
public int getY() {
return year;
}
}

// Comparator to sort movies by rating


class Rating implements Comparator<Movie> {
public int compare(Movie m1, Movie m2) {

// Sort by rating in descending order


return Double.compare(m2.getR(), m1.getR());
}
}

// Comparator to sort movies by name


class NameCompare implements Comparator<Movie> {
public int compare(Movie m1, Movie m2) {

// Sort by name in alphabetical order


return m1.getN().compareTo(m2.getN());
}
}

// Main class
public class Geeks {
public static void main(String[] args) {

// Create a list of movies


ArrayList<Movie> m = new ArrayList<>();
m.add(new Movie("Force Awakens", 8.3, 2015));
m.add(new Movie("Star Wars", 8.7, 1977));
m.add(new Movie("Empire Strikes Back", 8.8, 1980));

// Sort movies by rating and display all


Collections.sort(m, new Rating());
System.out.println("Movies sorted by rating:");
for (Movie m1 : m) {
System.out.println(m1.getR() + " " + m1.getN() + " " + m1.getY());
}
// Sort movies by name and display all
Collections.sort(m, new NameCompare());
System.out.println("\nMovies sorted by name:");
for (Movie m1 : m) {
System.out.println(m1.getN() + " " + m1.getR() + " " + m1.getY());
}
}
}

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.

// Java program to demonstrate Comparator interface


import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

class Movie {
private String name;
private double rating;
private int year;

// Constructor to initialize movie details


public Movie(String name, double rating, int year) {
this.name = name;
this.rating = rating;
this.year = year;
}

// Getter methods
public String getN() {
return name;
}
public double getR() {
return rating;
}
public int getY() {
return year;
}
}

// Comparator to sort movies by rating


class Rating implements Comparator<Movie> {
public int compare(Movie m1, Movie m2) {

// Sort by rating in descending order


return Double.compare(m2.getR(), m1.getR());
}
}

// Comparator to sort movies by name


class NameCompare implements Comparator<Movie> {
public int compare(Movie m1, Movie m2) {

// Sort by name in alphabetical order


return m1.getN().compareTo(m2.getN());
}
}

// Main class
public class Geeks {
public static void main(String[] args) {

// Create a list of movies


ArrayList<Movie> m = new ArrayList<>();
m.add(new Movie("Force Awakens", 8.3, 2015));
m.add(new Movie("Star Wars", 8.7, 1977));
m.add(new Movie("Empire Strikes Back", 8.8, 1980));

// Sort movies by rating and display all


Collections.sort(m, new Rating());
System.out.println("Movies sorted by rating:");
for (Movie m1 : m) {
System.out.println(m1.getR() + " " + m1.getN() + " " + m1.getY());
}

// Sort movies by name and display all


Collections.sort(m, new NameCompare());
System.out.println("\nMovies sorted by name:");
for (Movie m1 : m) {
System.out.println(m1.getN() + " " + m1.getR() + " " + m1.getY());
}
}
}

Output
Movies sorted by rating:
8.8 Empire Strikes Back 1980
8.7 Star Wars 1977
8.3 Force Awakens 2015

Movies sorted by name:


Empire Strikes Back 8.8 1980
Force Awakens 8.3 2015
Star Wars 8.7 1977

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.

Java 8 Optional Class


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 program without Optional Class

public class OptionalDemo {


public static void main(String[] args)
{
String[] words = new String[10];
String word = words[5].toLowerCase();
System.out.print(word);
}
}

Output:

Exception in thread "main" java.lang.NullPointerException

To avoid abnormal termination, we use the Optional class. In the following example, we are
using Optional. So, our program can execute without crashing.

The above program using Optional Class

// Java program with Optional Class

import java.util.Optional;

// Driver Class
public class OptionalDemo {
// Main Method
public static void main(String[] args)
{
String[] words = new String[10];

Optional<String> checkNull = Optional.ofNullable(words[5]);

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 :

 Final and immutable (though may contain references to mutable objects).

 Considered equal solely based on equals(), not based on reference equality(==).

 Do not have accessible constructors.

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 :

public static void geek(String name)


{
// code to be executed....
}

// Must have static modifier in their declaration.


// Return type can be int, float, String or user-defined data type.

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 :

public void geek(String name)


{
// code to be executed....
}
// Return type can be int, float String or user defined data type.

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 :

// Java program to illustrate


// optional class methods

import java.util.Optional;

class GFG {
// Driver code
public static void main(String[] args)
{

// creating a string array


String[] str = new String[5];

// Setting value for 2nd index


str[2] = "Geeks Classes are coming soon";

// It returns an empty instance of Optional class


Optional<String> empty = Optional.empty();
System.out.println(empty);

// It returns a non-empty Optional


Optional<String> value = Optional.of(str[2]);
System.out.println(value);
}
}

Output
Optional.empty
Optional[Geeks Classes are coming soon]

Example 2 :

// Java program to illustrate


// optional class methods

import java.util.Optional;

class GFG {

// Driver code
public static void main(String[] args)
{

// creating a string array


String[] str = new String[5];

// Setting value for 2nd index


str[2] = "Geeks Classes are coming soon";

// It returns a non-empty Optional


Optional<String> value = Optional.of(str[2]);
// It returns value of an Optional.
// If value is not present, it throws
// an NoSuchElementException
System.out.println(value.get());

// It returns hashCode of the value


System.out.println(value.hashCode());

// It returns true if value is present,


// otherwise false
System.out.println(value.isPresent());
}
}

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 :

1. Local : Simplified date-time API with no complexity of timezone handling.

2. Zoned : Specialized date-time API to deal with various timezones.

 LocalDate/LocalTime and LocalDateTime API : Use it when time zones are NOT required.

// Java code for LocalDate


// / LocalTime Function
import java.time.*;
import java.time.format.DateTimeFormatter;

public class Date {

public static void LocalDateTimeApi()


{

// the current date


LocalDate date = LocalDate.now();
System.out.println("the current date is "+
date);

// the current time


LocalTime time = LocalTime.now();
System.out.println("the current time is "+
time);

// will give us the current time and date


LocalDateTime current = LocalDateTime.now();
System.out.println("current date and time : "+
current);
// to print in a particular format
DateTimeFormatter format =
DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss");

String formatedDateTime = current.format(format);

System.out.println("in formatted manner "+


formatedDateTime);

// printing months days and seconds


Month month = current.getMonth();
int day = current.getDayOfMonth();
int seconds = current.getSecond();
System.out.println("Month : "+month+" day : "+
day+" seconds : "+seconds);

// printing some specified date


LocalDate date2 = LocalDate.of(1950,1,26);
System.out.println("the republic day :"+date2);

// printing date with current time.


LocalDateTime specificDate =
current.withDayOfMonth(24).withYear(2016);

System.out.println("specific date with "+


"current time : "+specificDate);
}

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

// Java code for Zoned date-time API


import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

public class Zone {

// Function to get Zoned Date and Time


public static void ZonedTimeAndDate()
{
LocalDateTime date = LocalDateTime.now();
DateTimeFormatter format1 =
DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss");

String formattedCurrentDate = date.format(format1);

System.out.println("formatted current Date and"+


" Time : "+formattedCurrentDate);

// to get the current zone


ZonedDateTime currentZone = ZonedDateTime.now();
System.out.println("the current zone is "+
currentZone.getZone());

// getting time zone of specific place


// we use withZoneSameInstant(): it is
// used to return a copy of this date-time
// with a different time-zone,
// retaining the instant.
ZoneId tokyo = ZoneId.of("Asia/Tokyo");

ZonedDateTime tokyoZone =
currentZone.withZoneSameInstant(tokyo);

System.out.println("tokyo time zone is " +


tokyoZone);

DateTimeFormatter format =
DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss");

String formatedDateTime = tokyoZone.format(format);

System.out.println("formatted tokyo time zone "+


formatedDateTime);

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

 Period and Duration classes :


Period : It deals with date based amount of time.
Duration : It deals with time based amount of time.

// Java code for period and duration


import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Month;
import java.time.Duration;
import java.time.Period;

public class Geekforgeeks {

public static void checkingPeriod()


{
LocalDate date1 = LocalDate.now();

LocalDate date2 =
LocalDate.of(2014, Month.DECEMBER, 12);

Period gap = Period.between(date2, date1);


System.out.println("gap between dates "+
"is a period of "+gap);
}

// Function to check duration


public static void checkingDuration()
{

LocalTime time1 = LocalTime.now();


System.out.println("the current time is " +
time1);

Duration fiveHours = Duration.ofHours(5);

// adding five hours to the current


// time and storing it in time2
LocalTime time2 = time1.plus(fiveHours);

System.out.println("after adding five hours " +


"of duration " + time2);

Duration gap = Duration.between(time2, time1);


System.out.println("duration gap between time1" +
" & time2 is " + gap);
}

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

 ChronoUnits Enum : java.time.temporal.ChronoUnit enum is added in Java 8 to replace


integer values used in old API to represent day, month etc.

// Java code for ChronoUnits Enum


import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class Geeksforgeeks {

// Function to check ChronoUnit


public static void checkingChronoEnum()
{
LocalDate date = LocalDate.now();
System.out.println("current date is :" +
date);

// adding 2 years to the current date


LocalDate year =
date.plus(2, ChronoUnit.YEARS);

System.out.println("next to next year is " +


year);

// adding 1 month to the current date


LocalDate nextMonth =
date.plus(1, ChronoUnit.MONTHS);

System.out.println("the next month is " +


nextMonth);

// adding 1 week to the current date


LocalDate nextWeek =
date.plus(1, ChronoUnit.WEEKS);

System.out.println("next week is " + nextWeek);

// adding 2 decades to the current date


LocalDate Decade =
date.plus(2, ChronoUnit.DECADES);

System.out.println("20 years after today " +


Decade);
}

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

 TemporalAdjuster : It is used to perform various date related operations.

// Java code Temporal Adjuster


import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import java.time.DayOfWeek;

public class Geek


{

// Function to check date and time


// according to our requirement
public static void checkingAdjusters()
{

LocalDate date = LocalDate.now();


System.out.println("the current date is "+
date);

// to get the first day of next month


LocalDate dayOfNextMonth =
date.with(TemporalAdjusters.
firstDayOfNextMonth());

System.out.println("firstDayOfNextMonth : " +
dayOfNextMonth );

// get the next saturday


LocalDate nextSaturday =
date.with(TemporalAdjusters.
next(DayOfWeek.SATURDAY));

System.out.println("next saturday from now is "+


nextSaturday);

// first day of current month


LocalDate firstDay =
date.with(TemporalAdjusters.
firstDayOfMonth());
System.out.println("firstDayOfMonth : " +
firstDay);

// last day of current month


LocalDate lastDay =
date.with(TemporalAdjusters.
lastDayOfMonth());

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

Default Methods In Java 8

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.

// A simple program to Test Interface default


// methods in java
interface TestInterface
{
// abstract method
public void square(int a);

// default method
default void show()
{
System.out.println("Default Method Executed");
}
}

class TestClass implements TestInterface


{
// implementation of square abstract method
public void square(int a)
{
System.out.println(a*a);
}

public static void main(String args[])


{
TestClass d = new TestClass();
d.square(4);

// default method executed


d.show();
}
}

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.

// A simple Java program to TestClassnstrate static


// methods in java
interface TestInterface
{
// abstract method
public void square (int a);

// static method
static void show()
{
System.out.println("Static Method Executed");
}
}

class TestClass implements TestInterface


{
// Implementation of square abstract method
public void square (int a)
{
System.out.println(a*a);
}

public static void main(String args[])


{
TestClass d = new TestClass();
d.square(4);

// Static method executed


TestInterface.show();
}
}

Output:

16
Static Method Executed

Default Methods and Multiple Inheritance

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.

// A simple Java program to demonstrate multiple


// inheritance through default methods.
interface TestInterface1
{
// default method
default void show()
{
System.out.println("Default TestInterface1");
}
}

interface TestInterface2
{
// Default method
default void show()
{
System.out.println("Default TestInterface2");
}
}

// Implementation class code


class TestClass implements TestInterface1, TestInterface2
{
// Overriding default show method
public void show()
{
// use super keyword to call the show
// method of TestInterface1 interface
TestInterface1.super.show();

// use super keyword to call the show


// method of TestInterface2 interface
TestInterface2.super.show();
}

public static void main(String args[])


{
TestClass d = new TestClass();
d.show();
}
}

Output:

Default TestInterface1
Default TestInterface2

Important Points:

1. Interfaces can have default methods with implementation in Java 8 on later.


2. Interfaces can have static methods as well, similar to static methods in classes.
3. Default methods were introduced to provide backward compatibility for old interfaces
so that they can have new methods without affecting existing code.

Factory methods for collections(like List, Map,


Set and Map.Entry)

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:

Empty List example:

List immutableList = List.of();

Non-Empty List example:

List immutableList = List.of("one", "two", "three");

Map has two set of methods: of() methods and ofEntries() methods to create an Immutable
Map object and an Immutable Map.Entry object respectively.

Empty Map Example:

jshell> Map emptyImmutableMap = Map.of()


emptyImmutableMap ==> {}

Non-Empty Map Example:

jshell> Map nonemptyImmutableMap = Map.of(1, "one", 2, "two", 3, "three")


nonemptyImmutableMap ==> {2=two, 3=three, 1=one}
JShell: The Interactive Java REPL
Oracle Corp. has introduced a new tool called “jshell”. It stands for Java Shell and also known as
REPL (Read Evaluate Print Loop). Many languages already feature an interactive Read-Eval-Print-
Loop, and Java now joins this club. It is used to execute and test any Java Constructs like class,
interface, enum, object, statements etc. very easily. You can launch jshell from the console and
directly start typing and executing Java code. The immediate feedback of jshell makes it a great
tool to explore APIs and try out language features.

Private Methods in Interfaces


In Java 8, we can provide method implementation in Interfaces using Default and Static
methods. However, we cannot create private methods in Interfaces. To avoid redundant code
and more re-usability, Oracle Corp. introduced private methods in Java SE 9 Interfaces. From
Java SE 9 on-wards, we can write private and private static methods too in an interface using
‘private’ keyword.

public interface Card{


private Long createCardID(){
// Method implementation goes here.
}

private static void displayCardDetails(){


// Method implementation goes here.
}
}

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?

CompletableFuture is a class in java.util.concurrent package that implements the Future and


CompletionStage Interface. It represents a future result of an asynchronous computation. It can
be thought of as a container that holds the result of an asynchronous operation that is being
executed in a different thread. It provides a number of methods to perform various operations
on the result of the async computation.

Creating a CompletableFuture

To create an instance of CompletableFuture, we can use the static


method supplyAsync provided by CompletableFuture class which takes Supplier as an
argument. Supplier is a Functional Interface that takes no value and returns a result.

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

One of the powerful features of CompletableFuture is its ability to compose multiple


asynchronous operations. We can use methods like thenApply, thenCombine, thenCompose to
perform operations on the result of one CompletableFuture and create a new
CompletableFuture as a result.

Example:

/*package whatever //do not write package name here */

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

// Running multiple tasks in parallel


CompletableFuture<Void> allOf = CompletableFuture.allOf(f1, f2);
// Waits for both futures to complete
allOf.join();

System.out.println("Future1 Result: " + f1.get());


System.out.println("Future2 Result: " + f2.get());
}
}

Output
Future1 Result: 10
Future2 Result: 20

Note: The allOf() method is used to combine the results of multiple CompletableFuture
instances.

Handling Exception in CompletableFuture

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

// 0 - returned by exceptionally block


System.out.println(resultFuture.get());
}
}

Output:

CompletableFuture API Improvements

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:

 New Factory Methods

 Support for Delays

 Improved Subclassing Support

 New Methods for Handling Result Transformation

 Improved Exception Handling

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:

1. Publisher: interface represents a provider of a potentially unbounded number of


sequenced elements.

2. Subscriber: interface represents a consumer of a potentially unbounded number of


sequenced elements

3. Subscription: interface represents link between "Publisher" and "Subscriber".


4. Processor: interface represents a processing stage which is both "Subscriber" and
"Publisher".

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.

Publishers, Subscribers, and Subscriptions

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

If an entity is both a publisher and a subscriber, it is called a processor. A processor commonly


acts as an intermediary between another publisher and subscriber (either of which may be
another processor), performing some transformation on the stream of data. For example, a
processor can be created that filters out items that match some criteria before passing them
onto its subscriber. A visual representation of a processor is illustrated in the figure below.
With a foundational understanding of how reactive streams operate, we can transform these
concepts into the realm of Java by codifying them into interfaces.

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:

public interface Publisher<T> {


public void subscribe(Subscriber<? super T> s);
}

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:

public interface Subscriber<T> {


public void onSubscribe(Subscription s);
public void onNext(T t);
public void onError(Throwable t);
public void onComplete();
}

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:

public interface Subscription {


public void request(long n);
public void cancel();
}
Lastly, we define a processor as a combination of the publisher and subscriber interfaces, with
an important quirk: A processor may produce items of a different type than the type of the
items it consumes. Therefore, we will use the formal generic type parameter T to represent the
type of the items the processor consumes and R to represent the type of the items it returns (or
produces). Note that an implementation of a producer may consume and produce items of the
same type, but there is no compile-time restriction that it must do so. This results in the
following interface:

public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {}

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.

How Do Reactive Streams Work in Java?

The standard Java port of the Reactive Streams interfaces is found in


the java.util.concurrent.Flow class and are bundled as static interfaces within the Flow class.
With the JavaDocs removed, the Flow class is defined as follows:

public final class Flow {

private Flow() {} // uninstantiable

@FunctionalInterface
public static interface Publisher<T> {
public void subscribe(Subscriber<? super T> subscriber);
}

public static interface Subscriber<T> {


public void onSubscribe(Subscription subscription);
public void onNext(T item);
public void onError(Throwable throwable);
public void onComplete();
}
public static interface Subscription {
public void request(long n);
public void cancel();
}

public static interface Processor<T,R> extends Subscriber<T>, Publisher<R> {}


}

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:

public class PrintSubscriber implements Subscriber<Integer> {

private Subscription subscription;

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

public class SubmissionPublisherExample {

public static void main(String... args) throws InterruptedException {


SubmissionPublisher<Integer> publisher = new SubmissionPublisher<>();
publisher.subscribe(new PrintSubscriber());

System.out.println("Submitting items...");

for (int i = 0; i < 10; i++) {


publisher.submit(i);
}

Thread.sleep(1000);
publisher.close();
}
}

Running this example results in the following output:

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:

public class PlusTenProcessor extends SubmissionPublisher<Integer> implements


Subscriber<Integer> {

private Subscription subscription;

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

public class SubmissionPublisherExample {

public static void main(String... args) throws InterruptedException {

SubmissionPublisher<Integer> publisher = new SubmissionPublisher<>();


PlusTenProcessor processor = new PlusTenProcessor();
PrintSubscriber subscriber = new PrintSubscriber();
publisher.subscribe(processor);
processor.subscribe(subscriber);

System.out.println("Submitting items...");

for (int i = 0; i < 10; i++) {


publisher.submit(i);
}

Thread.sleep(1000);
publisher.close();
}
}

Running this example results in the following output:

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.

Difference Between Module and Package

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

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.

The modules in Java have the below options mentioned:

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

Name of the module


The name of the module is named on the basis on the reverse domain pattern. It is mainly used
to overcome the naming conflict in java. suppose we have the domain names as
geeksforgeeks.org the module name can be org.geeksforgeeks .

The next point is that it should contain what does the module contains and what does the
module export in the java

How to Create a module in 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 want to create a directory structure.

 We want to create a module declarator as we mentioned earlier which is used to describe


the module.

 we have to create a source code in that module

Step 1 : (Create a Directory Structure)

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.

Step 2 - Create a module declarator

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
}

Write the above mentioned code in the module-info.java file.

Step 3 - Create a source code file

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.

The source code is mentioned in the below block:

//Java program to create a source code file


class Main
{
public static void main(String args[])
{
System.out.println("Hello welcome geek and know about java 9 module system");
}
}

Output
Hello welcome geek and know about java 9 module system

Compilation of java Module

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.

Note: A module can contain one or many classes.

Compiled module descriptor [ module-info file]

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

You might also like