[go: up one dir, main page]

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

Unit 3 1

The document explains Functional Interfaces in Java, introduced in Java 8, which allow the use of lambda expressions for implementing single abstract methods without lengthy anonymous class implementations. It details the characteristics of functional interfaces, including their types (Function, Consumer, Predicate, Supplier), and provides examples of built-in functional interfaces like Runnable and Comparable. Additionally, it covers the syntax and advantages of lambda expressions, as well as method references as a shorthand for lambda expressions.

Uploaded by

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

Unit 3 1

The document explains Functional Interfaces in Java, introduced in Java 8, which allow the use of lambda expressions for implementing single abstract methods without lengthy anonymous class implementations. It details the characteristics of functional interfaces, including their types (Function, Consumer, Predicate, Supplier), and provides examples of built-in functional interfaces like Runnable and Comparable. Additionally, it covers the syntax and advantages of lambda expressions, as well as method references as a shorthand for lambda expressions.

Uploaded by

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

Ajay Kumar Garg Engineering College, Ghaziabad

Information Technology Department

Functional Interfaces introduced in Java 8 allow us to use a lambda expression to initiate the
interface's method and avoid using lengthy codes for the anonymous class implementation.

Various built-in interfaces were declared with @FunctionalInterface annotation and made
functional from Java 8.

They are of 4 types, Function, Consumer, Predicate, and Supplier.

What is Functional Interface in Java?

No function is independently present on its own in java. They are part of classes or interfaces.
And to use them we require either the class or the object of the respective class to call that
function.

Functional Interface in Java enables users to implement functional programming in Java. In


functional programming, the function is an independent entity.

The function can do anything a variable is capable to perform like passing a function as a
parameter, a function returned by another function, etc.

Functional interfaces were introduced in Java 8. A functional interface can contain only one
abstract method and it can contain any number of static and default (non-abstract) methods.

Abstract methods are methods that do not require implementation during their declaration and
must be overridden by the class implementing the interface.

Default methods can be directly used in a class implementing the interface as well as can be
overridden and redefined. Static methods are required to be called using the name of the
interface preceding the method name. These cannot be overridden by the classes implementing
the interface.

Functional Interface in Java is also called Single Abstract Method (SAM) interface. From
Java 8 onwards, to represent the instance of functional interfaces, lambda expressions are used.

Lambda expressions are anonymous (don't have names) shortcode blocks that can take
parameters and return a value just like methods.

1
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

@FunctionalInterface

public interface interfaceName

// Abstract method

void myMethod();

// Default methods (optional)

default void defaultMethod()

// method implementation

// Static methods (optional)

static void staticMethod()

// method implementation

// public class

public class className

// main method

public static void main(String[] args)

2
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

interfaceName temp = (/*parameters*/) ->

// perform operations

};

temp.methodName(); // call abstract method of the interface

The functional interface in Java is defined just like normal interfaces. It should only have one
abstract method. Though it can contain multiple default or static methods.

Abstract keyword: Though from Java 8, interfaces can have static and default methods. By
default, methods in interfaces are abstract only. So it is not mandatory to mention
the abstract keyword before the method.

@FunctionalInterface Annotation @FunctionalInterface Annotation is written above the


interface declaration. It effectively acts as a function thus, it can be passed as a parameter to a
method or can be returned as a value by a method. It is optional, but when mentioned java
compiler ensures that the interface has only one abstract method.

If we try to add more than one abstract method, the compiler flags an Unexpected
@FunctionalInterface annotation message.

To implement the abstract method of a functional interface in Java, we can either


use lambda expression or we can implement the interface to our class and override the method.
In the syntax above we are using a lambda expression.

// interface

@FunctionalInterface

3
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

interface Sample{

// abstract method

int calculate(int val);

// public class

public class Fi1{

public static void main(String[] args){

// implementing the abstract method of the interface

Sample solution = (int val) -> val+51;

// calling the method

System.out.println("Ans = "+ solution.calculate(51));

4
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

Some Built-in Functional interface in Java

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.

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

1. Consumer

2. Predicate

3. Function

4. Supplier

Two Ways to Use Functional Interfaces in a Class

1. Using Lambda Expression


2. By Implementing the Interface

Lambda Expression

Lambda expression is a new and important feature of Java which was included in Java SE 8. It
provides a clear and concise way to represent one method interface using an expression. It is
very useful in collection library. It helps to iterate, filter and extract data from collection.

5
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

The Lambda expression is used to provide the implementation of an interface which has
functional interface. It saves a lot of code. In case of lambda expression, we don't need to define
the method again for providing the implementation. Here, we just write the implementation
code.

Java lambda expression is treated as a function, so compiler does not create .class file.

A lambda expression is a way to implement the only abstract method in an interface


(functional interface) without writing a separate class that implements that interface.

Why use Lambda Expression

1. To provide the implementation of Functional interface.


2. Less coding.

Features of Lambda Expressions

1. Lambda expressions are used with functional interfaces. They cannot be used with
interfaces that have more than one abstract method.

2. Using lambda expressions, we can directly pass an instance referred by a functional


interface wherever it is required.

3. We don’t have to create classes that implement a functional interface to create objects.
We can use lambda expressions instead.

4. Lambda expressions are used heavily while working with collections.

Note1 -> Lambda expression is a way of implementing the only abstract method in a functional
interface without having a class that implements the functional interface.

Note2 -> Lambda expressions allows us to use interfaces without having their implementing
classes.

6
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

Java Lambda Expression Syntax

(argument-list) -> {body}

Java lambda expression is consisted of three components.

1) Argument-list: It can be empty or non-empty as well.


2) Arrow-token: It is used to link arguments-list and body of expression.
3) Body: It contains expressions and statements for lambda expression

No Parameter Syntax

() ->

//Body of no parameter lambda

Example
@FunctionalInterface //It is optional
interface Drawable
{
public void draw();
}

public class LambdaExpressionExample2


{
public static void main(String[] args)
{
int width=10;

//with lambda

7
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

Drawable d2=()->
{
System.out.println("Drawing "+width);
};
d2.draw();
}
}

One Parameter Syntax


(p1) ->
{
//Body of single parameter lambda
}

Example: we take a user's name and Greet them with a message.

import java.util.*;

@FunctionalI+nterface

interface PersonalGreet

String greeting(String name);

public class Fi2

public static void main(String[] args)

Scanner sc = new Scanner(System.in);

System.out.println("May I please know your Name?");

8
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

String name = sc.next();

PersonalGreet hello = (String temp) -> "Hello! "+temp;

System.out.println(hello.greeting(name));

Two Parameter Syntax

(p1,p2) ->
{
//Body of multiple parameter lambda
}

@FunctionalInterface
public interface Calculation
{

int calculate(int num1, int num2);

public class LambdaExpExample

public static void main(String[] args)

Calculation sum = (n1, n2) -> (n1 + n2);

int result = sum.calculate(10, 20);

System.out.println("Result of sum = " + result);

9
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

Calculation multiplication = (n1, n2) -> (n1 * n2);


int multiplyResult = multiplication.calculate(10, 20);
System.out.println("Result of multiplication = " + multiplyResult);

}
}
return statement in lambda expressions:
In lambda expressions, if there is just one statement, then there is no need for a return statement.
They only need to use curly braces{} for using return statement with single statement in
lambda expression.
//Lambda without return keyword in case of single statement (a,b) -> (a+b);
//Lambda with return keyword in case of single statement. Here curly braces are must
(a,b) -> { return (a+b); };

But when there are multiple statements, it is mandatory to use return statements.

1. Using Lambda Expression:

Here, we just have to import the interface. We are not required to implement it in our class to
use the Functional Interface in Java.

Lambda expressions are just like methods in java but they make the code short, clean, and
readable. They are also called anonymous functions. Thus, they are used more frequently with
functional interfaces in Java.

Syntax:

InterfaceName tempName = (<parameters>) ->

// implementation of the abstract method of the Functional Interface

10
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

};

// to use this method

tempName.absName();

InterfaceName is the functional interface we are going to use in our main method. We neither
have to implement an interface nor have to override the abstract method.

We can directly give implementation to the abstract method using a lambda expression. To call
this temporarily implemented method we have to use the syntax tempName.AbstractMethod().

Example: we take a user's name and Greet them with a message.

import java.util.*;

@FunctionalInterface

interface PersonalGreet

String greeting(String name);

public class Fi2

public static void main(String[] args)

Scanner sc = new Scanner(System.in);

System.out.println("May I please know your Name?");

String name = sc.next();

PersonalGreet hello = (String temp) -> "Hello! "+temp;

11
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

System.out.println(hello.greeting(name));

Explanation: In the example above, we are not required to override any method. We can give
implementation to the abstract method of the functional interface in the main method itself
using a lambda expression. It takes one String variable as an argument and returns another
String.

2. By Implementing the Interface

Another way is to implement the interface in our class. By doing so we will have to override
the abstract method. Then to call the method we are supposed to create the object of our
class and use the following syntax obj.AbstarctMethod().
Syntax:
@FunctionalInterface
interface Sample
{
void absMethod();
}
public class className implements Sample
{
@Override
public static void absMethod(){
// implementation
}

12
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

public static void main(String[] args){


className object = new className();
object.absMethod();
}
}

Explanation: In the example above Sample is an interface. Our class Main implements the
interface, thus it has to override the abstract method of the interface. To call this abstract method
in our main method we have to create the object of our Main class.

Example: Implement it by overriding the method

import java.util.*;

@FunctionalInterface

interface PersonalGreet

String greeting(String name);

public class MyFI implements PersonalGreet{

public static void main(String[] args){

Scanner sc = new Scanner(System.in);

System.out.println("May I please know your Name?");

String name = sc.next();

MyFI obj = new MyFI();

System.out.println(obj.greeting(name));

public String greeting(String name)

13
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

return "Hello! "+name;

Functional Interface Having Multiple Default and Static Classes

// interface implementation

@FunctionalInterface

interface StaticandDefaultMethods

// abstract method

int square(int x);

// default methods

default int add(int a, int b)

return a+b;

default int sub(int a, int b)

return a-b;

14
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

// static methods

static int multiply(int a, int b)

return a*b;

static int divide(int a, int b)

return a/b;

// public class

public class Fi3 implements StaticandDefaultMethods

public static void main(String[] args){

int a = 8;

int b = 6;

// object of test class

Fi3 t = new Fi3();

// default method called using class object

int add = t.add(a,b);

int sub = t.sub(a,b);

15
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

// static methods called directly by the interface name

int mul = StaticandDefaultMethods.multiply(a,b);

int div = StaticandDefaultMethods.divide(a,b);

System.out.println("Add="+add+" "+ "Sub="+sub+ " "+"Multi="+mul+ " "+


"div="+div);

// abstract method

public int square(int x)

return x*x;

Built-in Java Functional Interfaces

Java has pre-defined or built-in functional interfaces for commonly occurring cases.

A few of these interfaces are as follows-

• Runnable - It contains only the run() method. It is used to write applications that can
run on a separate thread.

16
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

• Comparable - This interface contains only the compareTo() method. The objects of the
class that implements the Comparable interface can be compared the objects of the class
that implements Comparable interface can be compared and sorted.

• ActionListener - It contains only actionPerformed() method. It is responsible for


handling all the action events like a mouse click on a component.

• Callable - It only contains the call() method. This method is used to monitor the
progress of a function being executed by a thread.

The most frequently used among these interfaces is comparable.

Method References

Method references are a new feature of Java 8. They are a short and easily readable writing
syntax for a lambda expression.

We use a method reference to refer to a functional interface method. We can replace a method
reference for our lambda expression whenever we just refer to a method with a lambda
expression.

A functional interface has only one abstract or unimplemented method other than
different default and static methods. It exhibits only a single functionality.

Method references refer to the method of functional interface. It is an easy way of writing
lambda expressions used to call a method.

It is represented using double colon (::) operator. Lambda expressions can be replaced with
method references.

Consider a lambda expression that is as follows:

str -> System.out.println(str)

By method reference, it will change to this:

System.out::println

17
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

In method reference, the :: operator divides the method name from the class or object name.

In the statement System.out::println, System is defined in the java.lang package, out is an


instance of the type PrintStream, which is a public and static member field of the System class,
and println() is a public method of all instances of the PrintStream class.

Method reference types

Method reference can be divided in 3 types

1. Method reference to a static method

2. Method reference to an instance method

3. Method reference to a Constructor

1. Method reference to a static method

syntax:

<class-name> :: <static-method-name>

@FunctionalInterface
public interface Executable
{
void execute();
}
public class MethodReferenceExample
{

//we will use this static method to refer execute() method of Executable interface

public static void executeMethod()


{
System.out.println("Executing...");
}

public static void main(String[] args) {

18
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

//static method reference


//below executeMethod() is referring to execute() method of
//Executable interface
Executable executable = MethodReferenceExample :: executeMethod;
executable.execute();

}
}

2. Reference to an instance method


An instance method can also be referred for the single abstract method of a functional interface.

Syntax :
<instance-variable-name> :: <instance-method-name>

@FunctionalInterface
public interface Executable {

void execute();

}
public class MethodReferenceExample {

public void executeMethod() {


System.out.println("Executing...");
}

public static void main(String[] args) {

Executable executable = new MethodReferenceExample() :: executeMethod;


executable.execute();

}
}

3. Reference to a constructor
A functional interface’s abstract method can also be referred by a constructor of a class.

Syntax :

19
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

<class-name> :: new

public class MethodReferenceExample {

//no-arg constructor
public MethodReferenceExample()
{
System.out.println("MethodReferenceExample object initialized.");
}

public static void main(String[] args) {

Runnable r = MethodReferenceExample :: new;


Thread t1 = new Thread(r);
t1.start();

}
}

Stream API

Stream API is a newly added feature to the Collections API in Java 8. A stream represents a
sequence of elements and supports different operations (Filter, Sort, Map, and Collect) from a
collection. We can combine these operations to form a pipeline to query the data.

Stream Operations can be defined in 3 steps


1. Elevate your collection from concrete implementation to stream
2. Compose all the Stream API’s intermediate operation. E.g. map, filter...
3. Reduce the stream back to collection using some terminal operation. E.g. collect, reduce.

20
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

Java 8 Stream API workflow

Java stream involves creation of stream, performing intermediate operations followed by


terminal operations on the stream. This process or this flow is known as the Stream pipeline. A
general high level stream work flow is:

As we can see, in the first step a stream is created with the given input. This input can be any
type of data ranging from arrays, collections also primitives. Then this stream is passed on to
the intermediate operations. These in turn produce the output streams and give them to the
terminal operations. As a result of which output is generated form the terminal operations.
As we can see stream has 3 step

1. Creation of java stream object


2. Intermediate operations on java stream
3. Terminal operation on java stream

Step 1: Creation of Java Stream


There are many different ways to create streams like using methods of Stream class in stream
APIs, methods using collections and arrays, Special streams (Stream of primitives), Stream of
Strings, Files as a stream etc.
Methods using collections and arrays are the most commonly used ways to create stream object.

These methods take collections and arrays as input to produce streams out of them.
1.1 Creating Empty Stream

The empty() method should be used in case of the creation of an empty stream:
Stream<String> stream = Stream.empty();

21
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

stream.forEach(System.out::println);
1.2 Create java stream from collections
While creating streams from collection object we can use .stream() method on the
collection object like lists, sets etc.
//Creation of simple stream from list
List<String> names = new Arraylist():
names.add(“Alpha”);
names.add(“Beta”);
Stream stream = names.stream();
//Creation of simple stream from Set
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
Stream stream = set.stream();

1.3 Create stream from Array using of() method


While creating streams from an array we need to use use .of() method of Stream.java
class

//Creation of simple stream from Array


String[] arrays ={ "Alpha", "Beta", "Theta", "Gamma"};
Stream stream = Stream.of(arrays );
1.4 From Stream.builder()
When a builder is used the desired type should be additionally specified in the right part
of the statement, otherwise, the build() method will create an instance of the Stream:
Stream<String> streamBuilder =
Stream.<String>builder().add("a").add("b").add("c").build();
streamBuilder.forEach(System.out::println);

1.5 From Stream.generate()


Stream<String> streamGenerated = Stream.generate(() -> "element").limit(10);
streamGenerated.forEach(System.out::println);
1.6 From Stream.iterate()
Stream<Integer> streamIterated = Stream.iterate(1, n -> n + 2).limit(5);

22
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

streamIterated.forEach(System.out::println);
1.7 Stream of File
Java NIO class Files allows to generate a Stream of a text file through the lines() method.
Every line of the text becomes an element of the stream:
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class StreamCreationExamples {


public static void main(String[] args) throws IOException {

Path path = Paths.get("C:\\file.txt");


Stream<String> streamOfStrings = Files.lines(path);
Stream<String> streamWithCharset = Files.lines(path, Charset.forName("UTF-8"));
streamOfStrings.forEach(System.out::println);
streamWithCharset.forEach(System.out::println);
streamOfStrings.close();
streamWithCharset.close();
}
}
1.8 Stream of Primitives
As Stream is a generic interface and there is no way to use primitives as a type parameter
with generics, three new special interfaces were created: IntStream, LongStream,
DoubleStream.
Using the new interfaces alleviates unnecessary auto-boxing allows increased
productivity:
import java.io.IOException;

23
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

import java.util.Random;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;

public class StreamCreationExamples {


public static void main(String[] args) throws IOException {
IntStream intStream = IntStream.range(1, 3);
intStream.forEach(System.out::println);

LongStream longStream = LongStream.rangeClosed(1, 3);


longStream.forEach(System.out::println);

Random random = new Random();


DoubleStream doubleStream = random.doubles(3);
doubleStream.forEach(System.out::println);
}
}

Step 2: Intermediate operations on stream in java

The operations which returns source object (stream) after processing are known as intermediate
operations.(Also known as Non-Terminal operations).
As they return stream object we can again call another intermediate or terminal operation on
them. This helps to create chained operations one after the other consequently [Chain of
Responsibility Design Pattern in Java] , to perform multiple operations on same element. In
chained operation the output of one operation becomes the input of next.
Most important point to note stream operations does not change source objects, it returns new
stream with updated elements.
The most common operations on java 8 steam object

24
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

• Filter : used for conditional filtering the collection objects


• Map : convert object from one form to another
• Flatmap : Used for mapping nested objects in java stream
• Sorted : for sorting stream elements
2.1 Stream.filter():

The filter() method accepts a Predicate to filter all elements of the stream. This operation
is intermediate, enabling us to call another stream operation (e.g. forEach()) on the result.

memberNames.stream().filter((s) -> s.startsWith("A")).forEach(System.out::println);


2.2 Stream.map()
The map() intermediate operation converts each element in the stream into another object via
the given function.
The following example converts each string into an UPPERCASE string. But we can
use map() to transform an object into another type as well.
memberNames.stream().filter((s) -> s.startsWith("A"))
.map(String::toUpperCase)
.forEach(System.out::println);
2.3 Stream.sorted()
The sorted() method is an intermediate operation that returns a sorted view of the stream. The
elements in the stream are sorted in natural order unless we pass a custom Comparator.
memberNames.stream().sorted()
.map(String::toUpperCase)
.forEach(System.out::println);

Step 3: Terminal operation on stream


Terminal operation can produce result as primitive or collection or any Object or may not
produce any output. The intermediate operations always precede a terminal operation.
Although there can be multiple intermediate operations, but they must be followed by one and
only one terminal operation.
Examples of terminal methods are findAny(), allMatch(), forEach() etc.

25
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

3.1 Stream.collect() : The collect() method collects the result of the intermediate operations
performed on the stream and returns it.
List<Integer> numbers = new ArrayList<>(){4, 7, 1, 2};
Set square = numbers.stream().map( n -> n*n ).collect(Collectors.toSet());
3.2 Stream.forEach() : The forEach() method is used to iterate through every element of the
stream.
List<Integer> numbers = new ArrayList<>(){4, 7, 1, 2};
numbers.stream().map(x->x*x).forEach(y->System.out.println(y));
3.3 Stream.match()
Various matching operations can be used to check whether a given predicate matches the
stream elements. All of these matching operations are terminal and return a boolean result.
boolean matchedResult = memberNames.stream()
.anyMatch((s) -> s.startsWith("A"));
System.out.println(matchedResult); //true
matchedResult = memberNames.stream()
.allMatch((s) -> s.startsWith("A"));
System.out.println(matchedResult); //false
matchedResult = memberNames.stream()
.noneMatch((s) -> s.startsWith("A"));
System.out.println(matchedResult); //false

3.4 Stream.count()
The count() is a terminal operation returning the number of elements in the stream as
a long value.
long totalMatched = memberNames.stream()
.filter((s) -> s.startsWith("A"))
.count();
System.out.println(totalMatched); //2
3.5 Stream.reduce()

26
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

The reduce() method performs a reduction on the elements of the stream with the given
function. The result is an Optional holding the reduced value.
In the given example, we are reducing all the strings by concatenating them using a
separator #.
Optional<String> reduced = memberNames.stream()
.reduce((s1,s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);

An Example of How Java Streams Work:

The simplest way to think of Java streams is to imagine a list of objects disconnected from each
other, entering a pipeline one at a time. You can control how many of these objects are entering
the pipeline, what you do to them inside it, and how you catch them as they exit the pipeline.
We can obtain a stream from a collection using the .stream() method.
For example, we have a dropdown of languages in the header section and want to print the
dropdown list using both for loop & Stream API.
Let’s explain this with an array List example:
List<String>languages = new ArrayList <String>();
languages.add(“English”);
languages.add(“German”);
languages.add(“French”);
If we need to print the list of members,
Using For loop:
For(String language:languages)
{
System.out.println(language);
}
Usage of Stream API Methods:
Using stream API, we can obtain the stream by calling the .stream() method on the languages
array list and printing it in the following way:
Languages.stream().forEach(System.out::println)
After getting the stream, we called the forEach() method & pass the action we wanted to take
on each Element, then printed the member value on the console using the System.out.println
method.

27
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

After getting a stream from a collection, we can use that stream to process the collection’s
elements.
stream.Filter():
The filter() method is used to filter a stream. Let’s use the above stream example to filter out
the languages list based on specific criteria, i.e., items starting with ‘E.’
stream.filter(item->item.startsWith(“E”));
The filter() method takes a predicate as a parameter. The predicate interface contains a function
called boolean test(T t) that takes a single parameter and returns a Boolean. In this example,
we passed lambda expression (item->item.startsWith(“E”)) to the test() function.
When the filter() method is called on a stream, the filter passed as a parameter to the filter()
function is stored internally.
stream.sort():
We can sort a stream by calling the sort() method. Let’s use the sort() method on the above
languages list, as shown in the following code:
languages.stream().sorted().forEach(System.out::println);
The above method would help sort the elements in alphabetical order.
stream.map():
Stream provides a map() method to map the elements of a stream into another new object. Let’s
use the previous example and convert the elements of the languages list to uppercase, as shown
below:
language.stream().map(item->item.uppercase().forEach(System.out::println));
This will map all the elements that are strings in the language collection to their uppercase
equivalents.
stream.collect():
Stream API provides a collect() method for processing on the stream interface. When the
collect() method is invoked, filtering and mapping will occur, and the object obtained from
those actions will be collected. Let’s take the previous example and obtain a new list of
languages in uppercase, as shown in the below example:
List<String>
case language =
languages.stream().map(item>item.toUpperCase()).collect(collectors.toList()));
System.out.println(ucaselanguage);
First, it creates a stream, adds a map to convert the strings to uppercase, and collects all objects
in a new list.

28
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

stream.min() & max():


The Stream API provides the min() and max() methods to find the min & max values in streams.
These methods are used to find min & max values in different streams like streams of chars,
strings, dates, etc. We must change the parameter we pass in this method based on the stream
type.
Example:
max(Comparator. comparing (Integer::value of)): To get the max value from a stream of
numbers
Here you can see we have used Comparator. You are comparing() method to compare elements
of the stream, and this same method is used for comparing elements of all types of streams,
such as stream of chars, Strings, etc.
Example:
//Getting max number
Integer maximum =
Stream.of(10,13,4,9,2,100).max(Comparator.comparing(Integer::valueOf)).get();
//Getting min number
//getting max number
Integer minimum =
Stream.of(10,13,4,9,2,100).min(Comparator.comparing(Integer::valueOf)).get();
System.out.println(“Max number is: ”+maximum);
System.out.println(“Min number is: ”+minimum);
stream.count():
In Stream API, the Count method returns the number of elements in the stream after filtering.
So, let’s take the previous example to get the count of languages starting with ‘G.’
long count = languages.stream().filter(item->item.getName().startsWith(‘G’)).count();
count() method returns a long, which is the count of elements matching the filter criteria.

Common Operation in Stream API:

1. Filter: It is kind of conical operation, i.e. for ‘n’ inputs you would receive ‘0...n’ outputs.

2. Map: It is kind of cylinder operation, i.e. for ‘n’ input you would receive ‘transform (n)’
outputs.

3. Reduce: Operation which reduces the stream back to collection or some primitive values.

29
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

Base64 Encoding and Decoding in Java

Base64 encoding is a technique to convert binary data into an ASCII string format using a set
of 64 characters. It is commonly used in data transmission (like email and URL encoding),
storing binary data (like images or files) in text-based formats (like JSON or XML), and in
cryptographic applications.

Base64 decoding is the reverse process of encoding: it converts a Base64-encoded string back
into its original binary or text form.

The Base64 utility class is part of java.util class and provides static methods for Base64
encoding and decoding scheme.

Java 8 introduced the Base64 class.

This class supports three types of Base64 encoding –

Basic, URL, and Filename Safe, and MIME.

Following is a brief of these types added:

• Basic − Output is mapped to a set of characters lying in A-Za-z0-9+/. The encoder does
not add any line feed in output, and the decoder rejects any character other than A-Za-
z0-9+/.

• URL − Output is mapped to set of characters lying in A-Za-z0-9+_. Output is URL and
filename safe.

• MIME − Output is mapped to MIME friendly format. Output is represented in lines of


no more than 76 characters each, and uses a carriage return '\r' followed by a linefeed
'\n' as the line separator. No line separator is present to the end of the encoded output.

30
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

Base64 Basic Encoding

The Basic encoder uses the Base64 Alphabet for encoding and decoding. It will not add any
line separators to the encoded string. This encoder uses characters lying in A-Za-z0-9+/
character set.

We will use the getEncoder() method that returns a simple Base64.Encoder.

➔ This encoder uses the Basic type base64 encoding scheme.


➔ The encodeToString() method. It takes a byte array as input and returns an encoded string.

Base64 Encoding Process

Step-by-step:

1. Input: A byte array (e.g., string or binary file content).

2. Group: Every 3 bytes (24 bits) of input are split into four 6-bit groups.

3. Mapping: Each 6-bit group maps to a character in the Base64 alphabet:

A-Z => 0–25

a-z => 26–51

0–9 => 52–61

+ => 62

/ => 63

4. Padding: If input is not a multiple of 3 bytes, it's padded with = to make the output a
multiple of 4 characters.

import java.util.Base64;

public class Base641

public static void main(String args[])

31
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

String str = "Hello World";

byte[] b = str.getBytes();

String encodedStr = Base64.getEncoder().encodeToString(b);

System.out.print(encodedStr);

Base64 Encoding Without Padding

This encoding scheme added padding character(=) if the encoded string's length is not a
multiple of three.

import java.util.Base64;

public class Base641

public static void main(String args[])

String str = "java";

byte[] b = str.getBytes();

32
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

String encodedStr = Base64.getEncoder().encodeToString(b);

System.out.print(encodedStr);

→If we don't want this padding, we can use the withoutPadding() method on the encoder.

import java.util.Base64;
public class Base641
{
public static void main(String args[])
{
String str = "java";
byte[] b = str.getBytes();
String encodedStr=Base64.getEncoder().withoutPadding().encodeToString(b);

System.out.print(encodedStr);
}
}

33
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

Base64 Decoding

→To decode an encoded string, we will use Base64.Decoder returned by


the getDecoder() method. Then, we will use the decode() method of the decoder.

It will take an encoded string as input and returns the decoded string.

1. Each Base64 character is converted back to its 6-bit value.


2. Combine the bits to get the original byte stream.
3. Remove padding (=) to restore original content.

import java.util.Base64;

public class Base642

public static void main(String args[])

String str = "Hello World";

byte[] b = str.getBytes();

String encodedStr = Base64.getEncoder().encodeToString(b);

System.out.println("Encode String for "+str+" is->:"+encodedStr);

byte[] decodedByteArr = Base64.getDecoder().decode(encodedStr);

String decodedStr = new String(decodedByteArr);

System.out.println("Decoded String for "+ encodedStr + "is ->: " + decodedStr);

34
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

Base64 URL Encoding and Decoding by using the URL and Filename safe Base64
Alphabet

Base64 class handles URL encoding and decoding by using the URL and Filename safe
Base64 Alphabet. This encoder uses characters lying in A-Za-z0-9+/ character set. It encodes
using the URL and Filename safe type base64 encoding scheme.

We can use the getUrlEncoder() method to obtain a Base64 URL encoder. Then, we can use
the encodeToString() method.

Similarly, we have a getUrlDecoder() method that returns a URL decoder. Again, we can use
the decode() method with this decoder.

import java.util.Base64;

public class Base643

public static void main(String args[])

String urlToEncode = "https://aktu.ac.in";

byte[] b= urlToEncode.getBytes();

//Encoding

String encodedUrl = Base64.getUrlEncoder().encodeToString(b);

System.out.println("Encoded URL: " + encodedUrl);

//Decoding

35
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

byte[] decodedUrlBytes = Base64.getUrlDecoder().decode(encodedUrl);

String decodedUrl = new String(decodedUrlBytes);

System.out.print("Decoded URL: " + decodedUrl);

Base64 MIME Encoding and Decoding

MIME stands for Multipurpose Internet Mail Extension, and the Base64 class uses the
Base64 Alphabet for its encoding and decoding operations. MIME Base64 encoder encodes the
provided string content to MIME friendly format.

In the encoded output, each line contains a maximum of 76 characters.

Each line ends with a carriage return(\r) followed by a linefeed(\n) as the line separator.

Note that no line separator is present at the end of the encoded string.

We can use the getMimeEncoder() and the encodeToString() methods for the encoding.

import java.util.Base64;

import java.util.UUID;

public class Base644

public static void main(String args[])

36
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

//Creating a MIME input for encoding

StringBuilder sb = new StringBuilder();

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

sb.append(UUID.randomUUID().toString());

//Encoding

byte[] bytes = sb.toString().getBytes();

String mimeEncodedStr = Base64.getMimeEncoder().encodeToString(bytes);

System.out.println("Encoded String: " + mimeEncodedStr);

→To decode, we will use the getMimeDecoder() to obtain a decoder, and then we will use
the decode() method.

import java.util.Base64;

import java.util.UUID;

public class Base645

37
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

public static void main(String args[])

//Creating a MIME input for encoding

StringBuilder sb = new StringBuilder();

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

sb.append(UUID.randomUUID().toString());

//Encoding

byte[] bytes = sb.toString().getBytes();

String mimeEncodedStr = Base64.getMimeEncoder().encodeToString(bytes);

//Decoding

byte[] mimeDecodedBytes = Base64.getMimeDecoder().decode(mimeEncodedStr);

String mimeDecodedStr = new String(mimeDecodedBytes);

System.out.println("Encoded String: " + mimeDecodedStr);

38
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

Java forEach() Method

The forEach() method in Java is a utility function to iterate over a Collection (list, set or map)
or Stream. This method was introduced in Java 8 as part of the Java Stream API, which provides
a functional programming approach to processing sequences of elements.

→The forEach() performs a given Consumer action on each item in the Collection.

→The forEach method is a default method defined in the Iterable interface.

→It is commonly used with lambda expressions to provide a clear and concise way to loop
through elements.

→It accepts a single parameter of type Consumer, which is a functional interface. This makes
it ideal for lambda expressions and method references.

→It provides a more concise and readable way to iterate over collections compared to
traditional for-loops.

Here's the general syntax:

collection.forEach(action);

Where collection is any object that implements the Iterable interface, and action is an instance
of a class that implements the Consumer interface.

Takes Consumer function as input which contains the logic we need to implement. We can
implement the consumer function using lambda function or method reference.

List<String> list = Arrays.asList("Alex", "Brian", "Charles");

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

Since Java 8, the forEach() has been added in the following classes or interfaces:

• Iterable interface – This makes Iterable.forEach() method available to all collection


classes except Map

• Map interface – This makes forEach() operation available to all map classes.

39
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

• Stream interface – This makes forEach() and forEachOrdered() operations available


to all types of streams.

Internally, the forEach() uses the enhanced for-loop for iterating through the collection items.
So using the enhanced for-loop will give the same performance as forEach() method.

Example 1 : With List

import java.util.Arrays;

import java.util.List;

public class ForEachExample

public static void main(String[] args)

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// Using forEach with a lambda expression

names.forEach(name -> System.out.println(name));

// Using forEach with a method reference

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

Example 2 : With Map

The forEach method can also be used with a Map. Since a Map doesn't implement Iterable, we
need to use the forEach method on the entry set or key set.

import java.util.HashMap;

import java.util.Map;
40
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

public class ForEachMapExample

public static void main(String[] args)

Map<String, Integer> ages = new HashMap<>();

ages.put("Alice", 30);

ages.put("Bob", 25);

ages.put("Charlie", 35);

// Using forEach with a lambda expression

ages.forEach((name, age) -> System.out.println(name + " is " + age + " years old"));

// Using forEach with a method reference

ages.entrySet().forEach(System.out::println);

Example -3 with Streams:

import java.util.Arrays;

import java.util.List;

public class StreamForEachExample

public static void main(String[] args)

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// Using forEach with a stream

41
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

names.stream().forEach(name -> System.out.println(name));

names.stream().forEach(...) creates a stream from the list and then iterates over it, performing
the specified action.

Java try-with-resources

A resource is an object that must be closed after the program is finished with it.

• Resource may be a Database like MySQL, Oracle.


• Resource may be a file.
• Resource may be a socket connection Etc…

→When we connect to database, we must explicitly close database connection before JVM
shutdown.

→If we open a file we must explicitly close file before JVM shutdown and so on.

In order close these resources we generally use finally block (till java 6) since finally block
always executes irrespective of exceptions and errors.

The try with resources statement is a try statement that declares one or more resources.
The try with resources statement ensures that each resource is closed at the end of the try block
execution.

Any object that implements java.lang.AutoCloseable, and java.io.Closeable interfaces can be


used as a resource. AutoCloseable interface is also introduced in java 1.7

Its syntax is:

try (resource declaration)

42
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

// use of the resource

catch (ExceptionType e1)

// catch block

Example 1: try-with-resources

import java.io.*;

class Main

public static void main(String[] args)

String line;

try(BufferedReader br = new BufferedReader(new FileReader("test.txt")))

while ((line = br.readLine()) != null) {

System.out.println("Line =>"+line);

catch (IOException e)

System.out.println("IOException in try block =>" + e.getMessage());

43
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

Output if the test.txt file is not found.

IOException in try-with-resources block =>test.txt (No such file or directory)

Output if the test.txt file is found.

Entering try-with-resources block

Line =>test line

Advantages of using try-with-resources

Here are the advantages of using try-with-resources:

1. finally block not required to close the resource

Before Java 7 introduced this feature, we had to use the finally block to ensure that the
resource is closed to avoid resource leaks.

Example 2: Close resource using finally block

import java.io.*;

class Main {

public static void main(String[] args)

BufferedReader br = null;

String line;

try {

System.out.println("Entering try block");

br = new BufferedReader(new FileReader("test.txt"));

44
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

while ((line = br.readLine()) != null) {

System.out.println("Line =>"+line);

catch (IOException e)

System.out.println("IOException in try block =>" + e.getMessage());

finally

System.out.println("Entering finally block");

try

if (br != null)

br.close();

catch (IOException e)

System.out.println("IOException in finally block =>"+e.getMessage());

45
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

The try-with-resources statement does automatic resource management. We need not


explicitly close the resources as JVM automatically closes them. This makes the code more
readable and easier to write.

try-with-resources with multiple resources

We can declare more than one resource in the try-with-resources statement by separating them
with a semicolon;

Example 3: try with multiple resources

import java.io. *;

import java.util.*;

class Main {

public static void main(String[] args) throws IOException{

try (Scanner scanner = new Scanner(new File("testRead.txt"));

PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {

while (scanner.hasNext()) {

writer.print(scanner.nextLine());

When multiple declarations are made, the try-with-resources statement closes these resources
in reverse order. In this example, the PrintWriter object is closed first and then
the Scanner object is closed.

46
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

Java 9 try-with-resources enhancement

In Java 7, there is a restriction to the try-with-resources statement. The resource needs to be


declared locally within its block.

try (Scanner scanner = new Scanner(new File("testRead.txt")))

// code

If we declared the resource outside the block in Java 7, it would have generated an error
message.

Scanner scanner = new Scanner(new File("testRead.txt"));

try (scanner)

// code

To deal with this error, Java 9 improved the try-with-resources statement so that the reference
of the resource can be used even if it is not declared locally. The above code will now execute
without any compilation error.

import java.io.BufferedReader;

import java.io.FileReader;

import java.io.IOException;

public class TryWithResourcesJava9Example

47
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

public static void main(String[] args) {

BufferedReader reader = null;

try

reader = new BufferedReader(new FileReader("example.txt"));

try (reader) {

String line;

while ((line = reader.readLine()) != null) {

System.out.println(line);

catch (IOException e) {

System.err.println("Error reading the file: " + e.getMessage());

In this example:

• The BufferedReader is declared and initialized before the try block.

• The reader is then used directly in the try-with-resources statement without needing to
be re-declared.

• The resource reader will still be automatically closed when the try block is exited.

Key Benefits of Java 9 Enhancement

48
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

1. Conciseness: Reduces boilerplate code by avoiding re-declaration of resources.

2. Readability: Makes the code easier to read and understand by keeping the resource
initialization separate from the resource management.

3. Flexibility: Allows more flexible resource management, especially when resources are
initialized in different parts of the code before being used in the try-with-resources
statement.

Annotations

Java annotations are metadata (data about data) for our program source code.

They provide additional information about the program to the compiler but are not part of the
program itself. These annotations do not affect the execution of the compiled program.

Java annotations are defined using the @ symbol before the annotation name.

Categories of Annotations Types in Java

There are generally 5 types of annotations.

1. Marker Annotations:

49
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

These are the simplest type of annotation and do not contain any members or data. They
simply mark a declaration, providing information about its nature. Example: @Override.
2. Single-Value Annotations:
These annotations have one member. When applying them, the member's name can be
omitted if it is named "value." Example: @SuppressWarnings("unchecked").
3. Full Annotations:
These annotations have multiple members, each with a name and a
value. Example: @Retention(RetentionPolicy.RUNTIME).
4. Type Annotations:
Introduced in Java 8, these annotations can be applied to type usages, such as method return
types, field types, and type parameters. Example: @NotNull String message.
5. Repeatable Annotations:
Introduced in Java 8, these annotations can be applied multiple times to the same
declaration. They require the use of the @Repeatable meta-annotation. Example:

@interface Roles
{
Role[] value();
}

@Repeatable(Roles.class)
@interface Role {
String value();
}
@Role("Admin")
@Role("User")
class Security {
}
6. Predefined / Built-in Annotations:

Java comes with several built-in annotations, including:

1. @Override: Indicates that a method overrides a method declared in a superclass.

50
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

2. @Deprecated: Marks a method, class, or field as deprecated, indicating it should no


longer be used.

3. @SuppressWarnings: Instructs the compiler to suppress specified warning.

4. @SafeVarargs: Suppresses warnings about varargs usage.

5. @FunctionalInterface: Indicates that an interface is intended to be a functional


interface.

Example 1: @Override Annotation Example:

class A

public void displayInfo()

System.out.println("I am an in class A”);

class B extends A {

@Override

public void displayInfo()

System.out.println("Now I am in Class B.");

51
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

class Main {

public static void main(String[] args) {

B b = new B();

b.displayInfo();

7. Custom Annotations

We can also define our own annotations. using the @interface keyword. These annotations can
be used for various purposes, such as configuration, code generation, and aspect-oriented
programming.

Type Annotations

Before Java 8, annotations could be applied to declarations only. Now, type annotations can be
used as well. This means that we can place annotations wherever we use a type.

Previously, annotations could only be used on declarations (classes, methods, fields, etc.). With
type annotations, we can annotate almost any use of a type.

Type Annotations: Allow annotations to be used in more contexts (type uses).

Type Defnitions:

@NonNull String str;

This declaration specifies non-null variable str of type String to avoid NullPointerException.

@NonNull List<String> newList;

This declaration specifies a non-null list of type String.

52
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

Type casts:

newStr = (@NonNull String) str;

Generic type parameters:

Map<@NonNull String, @NonNull Integer> myMap;

Method returns:

public @NonNull String getName()

return name;

Method parameters:

public void setName(@NonNull String name) {

this.name = name;

Repeating Annotations in Java

Java 8 also introduced repeating annotations, which allow us to apply the same annotation
multiple times to a single element. To define a repeating annotation, we need to:

Repeating Annotations: Allow the same annotation to be applied multiple times to the same
element.

1. Define the annotation.

2. Define a container annotation that holds an array of the repeatable annotation.

3. Use the @Repeatable meta-annotation to associate the repeatable annotation with its
container.

Step 1: Define the Annotation

53
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Schedule {
String dayOfWeek();
String time();
}
Step 2: Define the Container Annotation
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Schedules {
Schedule[] value();
}
Step 3: Use the @Repeatable Meta-Annotation
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Schedules.class)
public @interface Schedule {
String dayOfWeek();
String time();
}
Using Repeating Annotations
Now we can apply the @Schedule annotation multiple times to the same element:
@Schedule(dayOfWeek = "Monday", time = "09:00")
@Schedule(dayOfWeek = "Wednesday", time = "09:00")

54
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

@Schedule(dayOfWeek = "Friday", time = "09:00")


public class MyClass {
// Class implementation
}

Java Modules

Java Platform Module System is a new feature added into Java 9 version. It allows us to collect
related packages and files into a single unit called a module.

Before Java 9, there was no concept of the module system that leads to increase the application
size and difficult to move around. But in Java 9, the whole JDK or Java APIs have
been restructured into a set of modules so that we can use only the required module for our
project. Java Module System is a higher level of aggregation above Java packages.

What is Java Modules?

Java modules is a way of aggregating or categorizing the Java APIs available in the Java core
platform, so that when we are developing a new Java application we can only include the
required Java libraries in our project, rather than having the complete Java platform APIs.

Java Modules can be System Modules or Application Modules.

Modules that are related to Java Core such as the Java SE and JDK are known as system
modules,

Modules that are designed to use modules and defined in the compiled module-info.class are
called application modules.

Since JDK 9 is built upon modular approach, we can check the built-in module by using the --
list-modules command into terminal or cmd.

java --list-modules

According to Java Specification, the key goals of modularizing the Java SE platform are

• Reliable configuration

55
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

• Strong encapsulation

• Scalable Java platform

• Greater platform integrity

• Improved performance

Apart from JDK, Java also allows us to create our own modules so that we can develop a
module-based application.

To use a module, including the jar file into module path instead of the classpath. A modular jar
file added to classpath is a normal jar file and the module-info.class file will be ignored.

How to Create Your own Module?

Java allows us to create user-defined modules by using these simple steps:

1. Create a directory that represents our module.

2. Create a Module Description file as module-info.java.

3. Java source code file.

For example, let's create a folder java9 that contains java.modules and it itself contains
a module-info.java (module-descriptive) file and a Java source file as Hello.java.

Arrange these files into this directory structure.

java9/

-java.modules

-Hello.java

-module-info.java

56
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

// Hello.java

package com.aktu;

public class Hello {

public static void main(String[] args) {

System.out.println("Hello, Modules!");

Module-Description file (module-info.java)

module java.modules

requires java.base;

The module descriptor file specifies the following point:

• Module’s name

• Module’s dependencies

• Module's visibility: specify the packages it explicitly makes available to other modules.

• Services that it offers

• Services that it consumes

• Reflection permission to other modules

The module name in the module-info file should be the same as the module name
directory(java.modules).

How to Compile Modular Java Source File?

We used the following command to compile our Java module.

57
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

javac --module-source-path java9/ -d java9/module-src -m java.modules

--module-source-path is a flag that represents the module location. Since our module is
located in the java9 folder, we used java9 in the command.

-d represents the directory to store our compiled module files. module-src is a directory in
which .class files will be stored.

-m points to the module name.

After successfully compiling the code. It will be stored into a separate directory and if we see
its directory structure then it looks like:

java9/

-java.modules

-Hello.java

-module-info.java

-module-src

-java.modules

-com

-aktu

-Hello.class

-module-info.class

How to Execute(Run) the Java Module?

After successfully compile the Java code, now let's run the code and check whether our code
produces the desired result. Use the below command.

java --module-path java9/module-src/ -m java.modules/com.aktu.Hello

After successful execution of the module, we get the result:

58
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

Hello, Modules

Anonymous Inner Class

An anonymous inner class in Java is a special type of inner class that doesn't have a name. It's
used to create a single object of a class that extends a superclass or implements an interface, all
within a single statement. This is useful when we need a simple, one-time-use implementation
without defining a separate named class.

An Anonymous Inner Class in Java is a type of inner class without a name, declared and
instantiated all in one expression. It's typically used to create one-time-use subclasses or
implementations of interfaces, especially when the implementation is short and used only
once.

Runnable task = new Runnable() {

@Override

public void run() {

System.out.println("Running in an anonymous inner class!");

};

task.run();

Here:

• Runnable is an interface.

• The new Runnable() { ... } defines an anonymous inner class implementing the
Runnable interface.

59
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

Diamond Operator with Anonymous Inner Class

Diamond operator is used to denote the enclosing type of a class.

For example: List<String> denotes list of strings, Set<Integer> denotes set of integers etc…
Empty diamond operator <> is introduced from Java 7 to implement automatic type inference
feature in the code.

Empty diamond operator removes the redundant code by leaving the generic type in the right
side of the declaration statement.

Till Java 9, empty diamond operator is allowed to use with normal classes only. It is not
allowed to use with anonymous inner classes.

From java 9 we can use diamond operator for anonymous inner classes with optional type
parameter. Diamond operator can be empty or can be with type parameter. If type parameter
is not specified then compiler of Java 9 will auto infers the type.

Diamond Operator : Before Java 7

Before Java 7, we need to explicitly mention type on both side of the declaration statement.

List<String> list = new ArrayList<String>();

Set<Integer> set = new HashSet<Integer>();

Map<Integer, String> map = new HashMap<Integer, String>();

→the same rule applies to anonymous inner classes also.

abstract class Addition<T>


{
abstract void add(T t1, T t2);
}

public class Java6DiamondOperator


{

60
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

public static void main(String[] args)


{
//Before Java 7, we need to mention type on both side of the declaration statement
Addition<Integer> integerAddition = new Addition<Integer>()
{
@Override
void add(Integer t1, Integer t2)
{
System.out.println(t1+t2);
}
};
}
}

Diamond Operator : After Java 7

With the introduction of empty diamond operator <> from Java 7, we need not mention type
on the right side of the declaration statement. We can leave empty inside the diamond operator.
Java compiler automatically determines the type on the right side of the declaration statement.

List<String> list = new ArrayList<>();

Set<Integer> set = new HashSet<>();

Map<Integer, String> map = new HashMap<>();

But, that rule doesn’t apply to anonymous inner classes. Empty diamond operator cannot be
used with anonymous inner classes.

For example, the below code gives compile time error if we run it in Java 7 environment.

abstract class Addition<T>

61
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

{
abstract void add(T t1, T t2);
}
public class Java7DiamondOperator
{
public static void main(String[] args)
{
//Compile time error
//'<>' cannot be used with anonymous classes
Addition<Integer> integerAddition = new Addition<>() {
@Override
void add(Integer t1, Integer t2)
{
System.out.println(t1+t2);
}
};
}
}
This issue has been resolved from Java 9
Diamond Operator : From Java 9
From Java 9, We can use empty diamond operator <> for anonymous inner classes also. The
above code doesn’t show any compile time errors if we execute it in Java 9 environment.
abstract class Addition<T>
{
abstract void add(T t1, T t2);
}

public class Java9DiamondOperator


{

62
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

public static void main(String[] args)


{
//No error, from Java 9

Addition<Integer> integerAddition = new Addition<>() {


@Override
void add(Integer t1, Integer t2)
{
System.out.println(t1+t2);
}
};
}
}

Local Variable Type Inference

Local Variable Type Inference, introduced in Java 10, allows us to declare local variables
without explicitly stating the type. Instead, we use the var keyword, and the compiler infers the

63
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

type of the variable based on the initializer expression. By using var, we don't need to use types
such as int, float, etc. It automatically infers the type of the variable based on the assigned
value. This feature is known as Local Variable Type Inference or LVTI in Java. This feature
is also sometimes referred to as "var declarations."

// Java 9 or older
int a = 10;
// Java 10 or higher
var a = 10;
Note: It is required to initialize the variable during declaration otherwise the compiler can't
infer the type and will generate an error.
Syntax and Examples

Here’s how we can use local variable type inference with the var keyword:

public class LVTypeInference {


public static void main(String[] args) {
// Explicit type declaration
String message = "Hello, Java 10!";
System.out.println(message);
// Local variable type inference using var
var greeting = "Hello, Java 10 with var!";
System.out.println(greeting);

// var with complex types


var numbers = new ArrayList<Integer>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
System.out.println("Numbers: " + numbers);
// var in for-each loop

64
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

var names = List.of("Alice", "Bob", "Charlie");


for (var name : names) {
System.out.println("Hello, " + name);
}
// var with lambda expressions
var runnable = (Runnable) () -> System.out.println("Running...");
runnable.run();
}
}

Switch Expressions

In Java, switch expressions are an enhancement of the traditional switch statement, introduced
in Java 12 (preview) and made standard in Java 14. Switch expressions allow us to use switch
in a more concise and expressive way, especially when returning a value.

Using switch expressions, we can return a value and we can use them within statements like
other expressions. We can use case L -> label to return a value or using yield, we can return a
value from a switch expression.

Switch Expressions are actually two enhancements that can be used independently but also
combined:

1. Arrow notation without break and fall-throughs

2. Using switch as an expression with a return value

Like all expressions, switch expressions evaluate to a single value and can be used in
statements.

They may contain "case L ->" labels that eliminate the need for break statements to prevent fall
through.

We can use a yield statement to specify the value of a switch expression.

65
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

Syntax and Usage

In traditional switch statements, we have multiple case labels and potentially a default label.
With switch expressions, we can use the switch keyword followed by a switch expression block
{ ... }.

Each case is handled using -> (arrow operator) to specify the actions to take for each case.

Switch Expression Using "case L ->" Labels

Java provides a new way to return a value from a switch expression using case L - > notation.

Syntax

case label1, label2, ..., labeln -> expression;|throw-statement;|block

public class SwitchExpressionExample

public static void main(String[] args) {

int dayOfWeek = 3;

String dayName = switch (dayOfWeek) {

case 1 -> "Monday";

case 2 -> "Tuesday";

case 3 -> "Wednesday";

case 4 -> "Thursday";

case 5 -> "Friday";

case 6 -> "Saturday";

66
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

case 7 -> "Sunday";

default -> "Invalid day";

};

System.out.println("Day name: " + dayName);

In this example:

• dayOfWeek is evaluated in the switch expression.

• Depending on its value, the corresponding case label (1, 2, ..., 7) determines the value
of dayName.

• The default label handles any unexpected or invalid values of dayOfWeek.

Switch Expression Using "case L:" Statements and the yield Statement

In java, we can get a value from switch expression and switch statement using yield statement.
yield statement returns the value and finishes the switch execution.

Syntax

case label1, label2, ..., labeln -> expression; yield value;

case label1:

case labeln: expression;

yield value;

The yield Keyword

67
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

In Java switch expressions, the yield keyword is used to return a value from a case block. This
is different from the break statement used in traditional switch statements, which terminates
the switch block without returning a value. The yield statement is specifically designed for
switch expressions, allowing them to produce a result.

String result = switch (day) {


case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" -> "Weekday";
case "Saturday", "Sunday" -> "Weekend";
default -> {
yield "Invalid day";
}
};

• When a case block matches the switch expression's value, and the block contains
a yield statement, the expression following yield is evaluated.

• The result of this evaluation becomes the value of the entire switch expression.

• The yield statement also terminates the execution of the switch expression, preventing
fall-through to subsequent case blocks.

The yield keyword is essential for switch expressions as it enables them to return values,
making them more functional and expressive compared to traditional switch statements.

Java 12 Switch Expressions

1. New Syntax to declare switch case labels: Case Label -> Expression
2. From Java 12, switch block can have multiple case labels separated by commas if they have
same set of statements to be executed.
3. From Java 12, we can return a value from switch block either through arrow operator (->)
or through break statements.
4. As switch block can return a value from Java 12, switch statements became switch
expressions.

68
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

5. Mixing of -> and : not allowed within a switch block. We should either use old method of
declaring case labels with colon (:) or new method of declaring case labels with arrow
operator (->) but not both.
6. break statement is optional if we use : to declare case labels. But, break statement is not
allowed if we are using -> for case labels.

Features and Benefits

1. Expression-Oriented: Switch expressions return a value (dayName in the example),


making them more versatile than traditional switch statements.

2. Arrow (->) Syntax: The arrow (->) separates the case label from the expression to be
evaluated when that case is matched.

3. Yield Keyword: Starting from Java 13, the yield keyword was introduced to explicitly
return a value from a switch branch. For example:

String dayName = switch (dayOfWeek) {

case 1, 2, 3, 4, 5 -> "Weekday";

case 6, 7 -> "Weekend";

default -> {

System.out.println("Invalid dayOfWeek: " + dayOfWeek);

yield "Unknown";

};

import java.util.Scanner;

public class SwEx2

69
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

public static void main(String[] args)

System.out.println("Enter score");

Scanner sc= new Scanner(System.in);

int score= sc.nextInt();

String grade = switch (score) {

case 10 -> "Outstanding";

case 9 -> "Excellent";

case 8 -> "Very Good";

default -> {

String result = "Failed";

yield result;

};

System.out.println(grade);

70
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

4. Multiple Constants in Case Labels: We can use multiple constants in a single case
label, separated by commas (case 1, 2, 3 -> ...).
5. Default Case: The default case is optional, but recommended to handle unexpected
values and ensure program reliability.

Java 12 Switch Expressions: Examples

1. Switch with String

import java.util.*;

public class SWex1

public static void main(String[] args)

System.out.println("Enter role: admin/user");

Scanner sc= new Scanner(System.in);

String role= sc.nextLine();

String message = switch (role)

71
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

case "admin" -> "Welcome, Admin!";

case "user" -> "Welcome, User!";

default -> "Access Denied";

};

System.out.println(message);

What's Different?

• Uses -> (arrow) syntax.

• Returns a value directly.

• No need for break.

• Cleaner and more readable.

Text blocks

Before text blocks, multiline strings were typically written using concatenation or escape
sequences:

public class TraditionalStringExample

72
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

public static void main(String[] args) {

String html = "<html>\n" +

" <body>\n" +

" <p>Hello, Java!</p>\n" +

" </body>\n" +

"</html>\n";

System.out.println(html);

Text blocks, introduced as a preview feature in Java 13 and became a standard feature in Java
15, provide a more readable way to write multi-line strings in Java. They are particularly useful
for embedding blocks of HTML, SQL, JSON, or any other format that traditionally requires
escaping and concatenation.

Syntax and Examples

In Java, text blocks are denoted by triple double quotes """ and allow for:

• Multi-line Strings: Text blocks can span multiple lines without needing escape
characters like \n.

• Embedded Quotes: Quotes (") within the text can be used without escaping.

Here’s an example to illustrate text blocks:

public class TextBlocksExample {

public static void main(String[] args) {

String html = """

73
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

<html>

<body>

<p>Hello, Java!</p>

</body>

</html>

""";

System.out.println(html);

Key Features of Text Blocks

1. Whitespace Management: Leading and trailing blank lines and spaces are excluded
from the string content, allowing for cleaner formatting.

2. Escape Sequences: Escape sequences such as \n for newlines and \" for double quotes
are interpreted as literals within the text block.

3. Expressions: We can embed expressions inside text blocks using ${...}. These
expressions are evaluated at runtime and their results are concatenated into the string.

String name = "Alice";

String greeting = """

Hello, ${name}!

How are you today?

""";

4. Raw String Literals: Text blocks are also known as raw string literals because they
treat backslashes (\) and other escape sequences literally, simplifying the representation
of strings that contain backslashes.

74
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

Records

Records are a new feature introduced in Java 14 that provide a compact way to declare classes
whose main purpose is to store data. They are a form of transparent carrier for immutable data,
offering a concise syntax and built-in support for getters, equals(), hashCode(), and toString()
methods. Records streamline the creation of simple data-holding classes, promoting
immutability and enhancing code readability.

Syntax and Example

Syntax of a Record

A record is declared using the record keyword, followed by the record name and a list of
components (fields). Here’s a simple example:

public record Person(String name, int age) {}

In this example:

• Person is the record name.

• name and age are components (fields) of the record.

• Records are implicitly final and immutable, meaning their components cannot be
changed after initialization.

public class RecordExample


{
public static void main(String[] args) {
Person person = new Person("Alice", 30);
// Accessing components using accessor methods generated by the compiler
System.out.println("Name: " + person.name());

75
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

System.out.println("Age: " + person.age());


// toString() method automatically provided by the compiler
System.out.println("Person Record: " + person);
// Equals and hashCode methods are also generated based on the record components
Person anotherPerson = new Person("Alice", 30);
System.out.println("Equal records? " + person.equals(anotherPerson));
}
}

Sealed classes
Sealed classes, introduced in Java 17, are a new type of class that restricts which other classes
or interfaces can extend or implement them.
In Java, the sealed keyword is used to control and restrict the inheritance hierarchy of classes
and interfaces. It allows us to specify which classes or interfaces are allowed to extend or
implement a particular class or interface.
We can declare classes and interfaces using the sealed keyword in Java. The sealed keyword is
not considered as a modifier, but rather a way to control the inheritance relationships within a
hierarchy. We can use it at the class or interface level, not for methods or fields.

Syntax to declare Sealed class or interface


To declare a sealed class, we use the sealed modifier along with the permits clause to specify
which classes or interfaces are allowed to extend or implement it.
public sealed class ClassName permits Subclass1, Subclass2, ...
{
// class members
}

Here's an example:
public sealed class Shape permits Circle, Rectangle, Triangle {
// Class definition
}

76
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

In this example:
• sealed class Shape declares Shape as a sealed class.
• permits Circle, Rectangle, Triangle specifies that only classes Circle, Rectangle, and
Triangle can directly extend Shape.
Example:
public sealed class Account permits SavingsAccount, FixedDepositAccount { }

public final class SavingsAccount extends Account { } // Allowed

public final class FixedDepositAccount extends Account { } // Allowed

public final class CheckingAccount extends Account { } // Not Allowed

In this example, the Account class is declared as a sealed class with permits SavingsAccount,
and FixedDepositAccount, specifying that only these two classes can extend it. As a result, no
other classes (like CheckingAccount) can extend Account beyond the ones specified in the
permits clause. Each specific account type extends Account and can add its own methods and
properties specific to that account type.

To declare a sealed interface in Java, we use the sealed keyword along with the permits
clause to specify which classes or interfaces are allowed to implement/extend it.
public sealed interface InterfaceName permits Class1, Interface1, ... {
// Interface methods and constants
}

Example Of Sealed Class in Java


public sealed abstract class Shape permits Circle, Triangle
{
public abstract double area();
}
public final class Circle extends Shape
{
private final double radius;

77
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department

public Circle(double radius)


{
this.radius = radius;
}
@Override
public double area()
{
return Math.PI * radius * radius;
}
}
public final class Triangle extends Shape
{
private final double base;
private final double height;
public Triangle(double base, double height)
{
this.base = base;
this.height = height;
}
@Override
public double area()
{
return 0.5 * base * height;
}
}

78

You might also like