0% found this document useful (0 votes)
3 views58 pages

OOP Java Unit III Notes

The document discusses functional interfaces in Java, introduced in Java 8, which allow for cleaner and more readable code using lambda expressions. It outlines the types of functional interfaces such as Predicate, Consumer, Supplier, and Function, along with their usage and examples. Additionally, it explains the syntax and characteristics of lambda expressions, highlighting their advantages over traditional anonymous classes.

Uploaded by

enhyphen240700
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)
3 views58 pages

OOP Java Unit III Notes

The document discusses functional interfaces in Java, introduced in Java 8, which allow for cleaner and more readable code using lambda expressions. It outlines the types of functional interfaces such as Predicate, Consumer, Supplier, and Function, along with their usage and examples. Additionally, it explains the syntax and characteristics of lambda expressions, highlighting their advantages over traditional anonymous classes.

Uploaded by

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

Functional Interfaces: Functional interfaces were introduced in Java 8 along with lambda expression and

method references. These three features were added to boost functional programming in Java and to write
clean, readable code. For example, in order to call a function, first we had to create a class with the required
method, create a class instance, and the instance, needed to invoke the method or another way was to use an
anonymous class with the corresponding method.
With lambda expression, we can avoid the need for both concrete as well as anonymous class objects. A
functional interface assists one step ahead, as a lambda expression can implement the functional interface
easily as only one method is to be implemented. Functional interfaces have a single functionality to exhibit.
For example, a Comparable interface with a single method compareTo() is used for comparison purposes. But
it can have any number of default and static methods.
@FunctionalInterface Annotation: By functionality, any interface having a single abstract method is a
functional interface. Java provides a @FunctionalInterface annotation to mark an interface as a functional
interface so that the compiler can check if an interface is a functional interface or not. This annotation is
optional and is primarily to add a compiler check and to increase the code readability and maintenance.
Types of Functional Interfaces in Java: There are primarily four types of functional interfaces in Java.
Predicate Functional Interface: A predicate functional interface is the one whose method accepts one
argument and will return either true or false. A predicate functional interface is mainly used in comparison to
sort elements or to filter a value based on certain condition(s) applied on the input passed. Java provides
predicate functional interfaces for primitives as well as IntPredicate, DoublePredicate, and LongPredicate,
which accept only Integer, Double, and Long respectively.
Usage: Here, the predicate function is to return either true/false based on the value passed.
Predicate predicate = (value) -> value != 0; // or
Predicate predicate = (value) -> test(value);
Example:
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class Predicatedemo
{
public static void main(String args[])
{
List<Integer> numbers = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Predicate<Integer> isEvenNumber = n -> n %2 == 0;// filter odd numbers lambda expression
numbers = numbers.stream().filter(isEvenNumber).toList();
System.out.println(numbers);
}
}
Consumer Functional Interface: A consumer functional interface is one whose method accepts one
argument and will not return anything. A consumer functional interface is mainly used for side-effect
operations. For example, to print an element, to add a salutation, etc. There are other variants of Consumer as
well like BiConsumer. A BiConsumer functional interface can accept two parameters. Java provides consumer
functional interfaces for primitives as well as IntConsumer, DoubleConsumer, and LongConsumer, which
accept only Integer, Double, and Long respectively.
Usage: Consumer consumer = (value) -> System.out.println(value); // Or
Consumer consumer1 = System.out::println; // Or
Consumer consumer2 = (value) -> accept(value);
Example:
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class ConsumerDemo
{
public static void main(String args[])
{
List<Integer> numbers = Arrays.asList(1,2,3,4,5,6,7,8);
Consumer<Integer> consumer = (value) -> System.out.println(value);
Consumer consumer1 = System.out::println;
System.out.println("Printing using consumer functional interface as lambda expression");
numbers.forEach(consumer);
System.out.println("Printing using consumer functional interface as method reference");
numbers.forEach(consumer1);
}
}
Supplier Functional Interface: The supplier functional interface is the one whose method does not have any
arguments to pass and will return a value. A supplier functional interface is mainly used to generate values
lazily. For example, to get a random number, to generate a sequence of numbers, etc.
Usage: Supplier supplier = () -> Math.random() * 10; // or
Supplier supplier1 = () -> get();
Example:
//Using supplier functional interface to get a random number with the help of a lambda expression.
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class SupplierDemo
{
public static void main(String args[])
{
Supplier<Integer> supplier = () -> (int)(Math.random() * 10);
List<Integer> randomNumbers = new ArrayList<>();
// generate 10 random numbers
for(int i = 0; i< 10; i++) { randomNumbers.add(supplier.get()); }
System.out.println(randomNumbers);
}
}
Function Functional Interface: Function functional interface is one whose method accepts one argument
and will return a value. A function functional interface is mainly used to get the processed value. For example,
to get the square of an element, to trim string values, etc. There are other variants of Function as well like
BiFunction. A BiFunction functional interface can accept two parameters. Java provides function functional
interfaces for primitives as well as IntFunction, DoubleFunction, and LongFunction, which accept only
Integer, Double, and Long respectively. There are two more utility interfaces, UnaryOperator which extend
the Function interface, and BinaryOperator which extends the BiFunction interface.
Usage: Function function = (value) -> Math.random() * 10; // or
Function function1 = (value) -> apply(value);
Example: //Using function functional interface to get a list of squares of numbers with the help of a lambda
expression.
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
public class FunctionDemo
{
public static void main(String args[])
{
List<Integer> numbers = Arrays.asList(1,2,3,4,5,6,7,8);
Function<Integer, Integer> squared = (value) -> value * value;
List<Integer> squaredNumbers = numbers.stream().map(squared).toList();
System.out.println(squaredNumbers);
}
}
Existing Functional Interfaces Prior to Java 8: With Java, many existing interfaces are annotated as
functional interfaces and can be used in lambda expressions. For example:
 Runnable − provides run() method
 Callable − provides call() method
 Actionlistener − provides actionPerformed() method
 Comparable − provides compareTo() method to compare two numbers
Example:
public class RunnableDemo
{
public static void main(String args[])
{
// create anonymous inner class object
new Thread(new Runnable(){
@Override public void run()
{
System.out.println("Thread 1 is running");
}
}).start();
// lambda expression to create the object
new Thread(() -> {System.out.println("Thread 2 is running.");}).start();
}
}

Lambda Expression: The concept of lambda expression was first introduced in LISP (an AI based
programming language). It facilitates functional programming and simplifies development a lot. A lambda
expression works on the principle of functional interface. It can be seen as a function, which can be created
without a class. A lambda expression can infer the type of parameter used and can return a value without a
return keyword. It can also be passed like a parameter and can be executed when and as needed. These are a
concise representation of anonymous functions, which can be passed around. These are nameless functions ,
which can be passed as constant values. This means that they can be present anywhere where any other
constant value might have been present, but are typically written as a parameter to some other function.
Following are some of the properties of a Lambda Expression:
 Anonymous: It can still be called as anonymous because it doesn’t have an explicit name.
 Concise: As mentioned the case with Anonymous classes, we write a lot less code with Lambdas as
compared to the Anonymous classes.
 Function: A Lambda is more like a function than a method. This is because a method belongs to a
class whereas a Lambda doesn’t. But just like a method, a Lambda accepts a list of parameters, has a
body and can throw exceptions as well.
 Can be passed as a parameter: A Lambda can be passed around to other functions just like a normal
parameter.
Lambda Expression Syntax: It is characterized by the following syntax: (parameter_list) -> {function_body}
Following are the important characteristics of a lambda expression:
 Optional Type Declaration: No need to declare the type of a parameter. The compiler can inference
the same from the value of the parameter.
 Optional parenthesis around parameter: No Need to declare a single parameter in parenthesis. For
multiple parameters, parenthesis are required.
 Optional Curly braces: No need to use curly braces in expression body if the body contains a single
statement.
 Optional return keyword: The compiler automatically returns the value if the body has a single
expression to return the value. Curly braces are required to indicate that expression returns a value.
Lambda expression vs method in Java: A method (or function) in Java has these main parts:
1. Name
2. Parameter list
3. Body
4. return type.
A lambda expression in Java has these main parts: Lambda expression only has body and parameter list.
1. No name – function is anonymous so we don’t care about the name
2. Parameter list
3. Body – This is the main part of the function.
4. No return type – The java 8 compiler is able to infer the return type by checking the code. you need not to
mention it explicitly.
Where to use the Lambdas in Java: To use lambda expression, you need to either create your own functional
interface or use the pre defined functional interface provided by Java. An interface with only single abstract
method is called functional interface(or Single Abstract method interface), for example: Runnable, callable,
ActionListener etc. To use function interface:
Pre Java 8: We create anonymous inner classes.
Post Java 8: You can use lambda expression instead of anonymous inner classes.
Example: Using Lambda with Functional Interfaces: Prior to java 8 we used the anonymous inner class to
implement the only abstract method of functional interface. Lambda functions are primarily used with
functional interfaces (interfaces with a single abstract method). For example, the Runnable interface:
Without Lambda:
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Running without lambda!");
}
};
r.run();
With Lambda:
Runnable r = () -> System.out.println("Running with lambda!");
r.run();

Java Lambda expression Example: Without using Lambda expression:


import java.awt.*;
import java.awt.event.*;
public class ButtonListenerOldWay
{
public static void main(String[] args)
{
Frame frame=new Frame("ActionListener Before Java8");
Button b=new Button("Click Here");
b.setBounds(50,100,80,50);
b.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){ System.out.println("Hello World!"); } });
frame.add(b);
frame.setSize(200,200);
frame.setLayout(null);
frame.setVisible(true);
}
}
By using Lambda expression: Instead of creating anonymous inner class, we can create a lambda
expression like this:
import java.awt.*;
public class ButtonListenerNewWay
{
public static void main(String[] args)
{
Frame frame=new Frame("ActionListener java8");
Button b=new Button("Click Here");
b.setBounds(50,100,80,50);
b.addActionListener(e -> System.out.println("Hello World!"));
frame.add(b);
frame.setSize(200,200);
frame.setLayout(null);
frame.setVisible(true);
}
}
Note:
1. As you can see that we used less code with lambda expression.
2. Backward compatibility: You can use the lambda expression with your old code. Lambdas are backward
compatible so you can use them in existing API when you migrate your project to java 8.
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: // Java program to demonstrates Lambda expression with zero parameter
@FunctionalInterface
interface ZeroParameter {
void display();
}
public class Demo {
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();
}
}
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); });
}
}
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));
}
}
Example 1: Java Lambda Expression with no parameter
@FunctionalInterface
interface MyFunctionalInterface
{
//A method with no parameter
public String sayHello();
}
public class Example
{
public static void main(String args[])
{
// lambda expression
MyFunctionalInterface msg = () -> {return "Hello"; };
System.out.println(msg.sayHello());
}
}
Example 2: Java Lambda Expression with single parameter
@FunctionalInterface
interface MyFunctionalInterface
{
//A method with single parameter
public int incrementByFive(int a);
}
public class Example
{
public static void main(String args[])
{
// lambda expression with single parameter num
MyFunctionalInterface f = (num) -> num+5;
System.out.println(f.incrementByFive(22));
}
}
Example 3: Java Lambda Expression with Multiple Parameters
interface StringConcat
{
public String sconcat(String a, String b);
}
public class Example
{
public static void main(String args[])
{
// lambda expression with multiple arguments
StringConcat s = (str1, str2) -> str1 + str2;
System.out.println("Result: "+s.sconcat("Hello ", "World"));
}
}
Example 4: Iterating collections using foreach loop
import java.util.*;
public class Example
{
public static void main(String[] args)
{
List<String> list=new ArrayList<String>();
list.add("Rick");
list.add("Negan");
list.add("Daryl");
list.add("Glenn");
list.add("Carl");
list.forEach( (names)->System.out.println(names) );
}
}
We can iterate Maps and other collection classes using lambda expression,
Example 5:
public class TesterDemo
{
final static String salutation = "Hello! ";
public static void main(String args[])
{
TesterDemo tester = new TesterDemo();
//with type declaration
MathOperation addition = (int a, int b) -> a + b;
//with out type declaration
MathOperation subtraction = (a, b) -> a - b;
//with return statement along with curly braces
MathOperation multiplication = (int a, int b) -> { return a * b; };
//without return statement and without curly braces
MathOperation division = (int a, int b) -> a / b;
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + tester.operate(10, 5, division));
//without parenthesis
GreetingService greetService1 = message -> System.out.println(salutation+ message);
//with parenthesis
GreetingService greetService2 = (message) -> System.out.println(salutation+ message);
greetService1.sayMessage("Akash");
greetService2.sayMessage("Vikash");
}
interface MathOperation
{
int operation(int a, int b);
}
interface GreetingService
{
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation)
{
return mathOperation.operation(a, b);
}
}

Lambda expressions are used primarily to define inline implementation of a functional interface, i.e., an
interface with a single method only. Lambda expression eliminates the need of anonymous class and gives a
very simple yet powerful functional programming capability to Java. In the above example, we've used various
types of lambda expressions to define the operation method of MathOperation interface. Then we have defined
the implementation of sayMessage of GreetingService.
Scope of Java Lambda Expression: Using lambda expression, you can refer to any final variable or
effectively final variable (which is assigned only once). Lambda expression throws a compilation error, if a
variable is assigned a value the second time
Using Constant in Lambda Expression: In the lambda expression, we can use constant without any
compilation error, like salutation constant used in above example.
Using Lambda Expression in Collections: From Java 8 onwards, almost all collections are enhanced to
accept lambda expression to perform operations on them. For example, to iterate a list, filter a list, to sort a
list and so on. In this example, we're showcasing how to iterate a list of string and print all the elements and
how to print only even numbers in a list using lambda expressions. Example Code is as follows:
import java.util.ArrayList;
import java.util.List;
public class DemoTester
{
public static void main(String args[])
{
// prepare a list of strings
List<String> list = new ArrayList<>();
list.add("java");
list.add("html");
list.add("python");
// print the list using a lambda expression
// here we're passing a lambda expression to forEach method of list object
list.forEach(i -> System.out.println(i));
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
numbers.add(4);
numbers.add(5);
numbers.add(6);
numbers.add(7);
numbers.add(8);
System.out.println(numbers);
// filter the list using a lambda expression
// here we're passing a lambda expression to removeIf
// method of list object where we can checking
// if number is divisible by 2 or not
numbers.removeIf( n -> n%2 != 0);
System.out.println(numbers);
}
}
Lambda expressions can be stored in variables if the variable's type is an interface which has only one method.
The lambda expression should have the same number of parameters and the same return type as that method.
Java has many of these kinds of interfaces built in, such as the Consumer interface (found in
the java.util package) used by lists.
Example6: //Use Java's Consumer interface to store a lambda expression in a variable:
import java.util.ArrayList;
import java.util.function.Consumer;
public class Main
{
public static void main(String[] args)
{
ArrayList<Integer> numbers = new ArrayList<Integer>();
numbers.add(5);
numbers.add(9);
numbers.add(8);
numbers.add(1);
Consumer<Integer> method = (n) -> { System.out.println(n); };
numbers.forEach( method );
}
}
To use a lambda expression in a method, the method should have a parameter with a single-method interface
as its type. Calling the interface's method will run the lambda expression:
Example 7: //Create a method which takes a lambda expression as a parameter
interface StringFunction
{
String run(String str);
}
public class Main
{
public static void main(String[] args)
{
StringFunction exclaim = (s) -> s + "!";
StringFunction ask = (s) -> s + "?";
printFormatted("Hello", exclaim);
printFormatted("Hello", ask);
}
public static void printFormatted(String str, StringFunction format)
{
String result = format.run(str);
System.out.println(result);
}
}
Example: Lambda with Parameters: Here’s an example using the Comparator interface:
Without Lambda:
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return a - b;
}
};
System.out.println(comparator.compare(10, 20)); // Output: -10
With Lambda:
Comparator<Integer> comparator = (a, b) -> a - b;
System.out.println(comparator.compare(10, 20)); // Output: -10
Example: Lambda with Streams: Lambdas are often used with Streams API for functional programming:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(name -> System.out.println(name)); // Output: Alice
Method References (Shortcut for Lambdas): If a lambda simply calls an existing method, you can use
method references:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(System.out::println); // Output: Alice, Bob, Charlie
Benefits of Lambda Functions:
 Conciseness: Reduces boilerplate code.
 Readability: Makes code easier to understand.
 Functional Programming: Enables a functional style of programming in Java.
Limitations:
 Lambdas can only be used with functional interfaces.
 Overuse can make debugging harder due to lack of meaningful names.
Practice Exercise: Try implementing a lambda function for the following:
1. Sort a list of integers in descending order.
2. Filter a list of strings to include only those with more than 3 characters.

Java – Streams: Stream is a new abstract layer introduced in Java 8. Using stream, you can process data in a
declarative way similar to SQL statements. Using collections framework in Java, a developer has to use loops
and make repeated checks. Another concern is efficiency; as multi-core processors are available at ease, a Java
developer has to write parallel code processing that can be pretty error-prone. To resolve such issues, Java 8
introduced the concept of stream that lets the developer to process data declaratively and leverage multicore
architecture without the need to write any specific code for it.
What is Stream in Java? Stream represents a sequence of objects from a source, which supports aggregate
operations. Following are the characteristics of a Stream:
 Sequence of elements: A stream provides a set of elements of specific type in a sequential manner. A
stream gets/computes elements on demand. It never stores the elements.
 Source: Stream takes Collections, Arrays, or I/O resources as input source.
 Aggregate operations: Stream supports aggregate operations like filter, map, limit, reduce, find,
match, and so on.
 Pipelining: Most of the stream operations return stream itself so that their result can be pipelined.
These operations are called intermediate operations and their function is to take input, process them,
and return output to the target. collect() method is a terminal operation which is normally present at
the end of the pipelining operation to mark the end of the stream.
 Automatic iterations: Stream operations do the iterations internally over the source elements
provided, in contrast to Collections where explicit iteration is required.
Generating Streams in Java: With Java 8, Collection interface has two methods to generate a Stream.
 stream(): Returns a sequential stream considering collection as its source.
 parallelStream(): Returns a parallel Stream considering collection as its source.
List<String> strings = Arrays.asList("nkm", "", "AC", "PKS", "DSSM","", "SKS");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
forEach Method: Stream has provided a new method 'forEach' to iterate each element of the stream. The
following code segment shows how to print 10 random numbers using forEach.
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
map Method: The 'map' method is used to map each element to its corresponding result. The following code
segment prints unique squares of numbers using map.
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
//get list of unique squares
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
filter Method: The 'filter' method is used to eliminate elements based on a criteria. The following code
segment prints a count of empty strings using filter.
List<String>strings = Arrays.asList("nkm", "", "AC", "PKS", "DSSM","", "SKS");
//get count of empty string
int count = strings.stream().filter(string -> string.isEmpty()).count();
limit Method: The 'limit' method is used to reduce the size of the stream. The following code segment shows
how to print 10 random numbers using limit.
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
sorted Method: The 'sorted' method is used to sort the stream. The following code segment shows how to
print 10 random numbers in a sorted order.
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
Parallel Processing: parallelStream is the alternative of stream for parallel processing. Take a look at the
following code segment that prints a count of empty strings using parallelStream.
List<String> strings = Arrays.asList("nkm", "", "AC", "PKS", "DSSM","", "SKS");
//get count of empty string
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();
It is very easy to switch between sequential and parallel streams.
Collectors: Collectors are used to combine the result of processing on the elements of a stream. Collectors
can be used to return a list or a string.
List<String>strings = Arrays.asList("nkm", "", "AC", "PKS", "DSSM","", "SKS");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("Filtered List: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("Merged String: " + mergedString);
Statistics: With Java 8, statistics collectors are introduced to calculate all statistics when stream processing is
being done.
List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Highest number in List : " + stats.getMax());
System.out.println("Lowest number in List : " + stats.getMin());
System.out.println("Sum of all numbers : " + stats.getSum());
System.out.println("Average of all numbers : " + stats.getAverage());
Java Streams Example:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.Map;
public class StreamDemo
{
public static void main(String args[])
{
System.out.println("Using Java 7: ");
List<String> strings = Arrays.asList("nkm", "", "AC", "PKS", "DSSM","", "SKS");
System.out.println("List: " +strings);
long count = getCountEmptyString(strings);
System.out.println("Empty Strings: " + count);
count = getCountLength(strings);
System.out.println("Strings of length 3: " + count);
List<String> filtered = deleteEmptyStrings(strings);
System.out.println("Filtered List: " + filtered);
String mergedString = getMergedString(strings,", ");
System.out.println("Merged String: " + mergedString);
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
List<Integer> squaresList = getSquares(numbers);
System.out.println("Squares List: " + squaresList);
List<Integer> integers = Arrays.asList(1,2,13,4,15,6,17,8,19);
System.out.println("List: " +integers);
System.out.println("Highest number in List : " + getMax(integers));
System.out.println("Lowest number in List : " + getMin(integers));
System.out.println("Sum of all numbers : " + getSum(integers));
System.out.println("Average of all numbers : " + getAverage(integers));
System.out.println("Random Numbers: ");
Random random = new Random();
for(int i = 0; i < 10; i++) { System.out.println(random.nextInt()); }
System.out.println("Using Stream API: ");
System.out.println("List: " +strings);
count = strings.stream().filter(string->string.isEmpty()).count();
System.out.println("Empty Strings: " + count);
count = strings.stream().filter(string -> string.length() == 3).count();
System.out.println("Strings of length 3: " + count);
filtered = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.toList());
System.out.println("Filtered List: " + filtered);
mergedString=strings.stream().filter(string->!string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("Merged String: " + mergedString);
squaresList = numbers.stream().map( i ->i*i).distinct().collect(Collectors.toList());
System.out.println("Squares List: " + squaresList);
System.out.println("List: " +integers);
IntSummaryStatistics stats = integers.stream().mapToInt((x) ->x).summaryStatistics();
System.out.println("Highest number in List : " + stats.getMax());
System.out.println("Lowest number in List : " + stats.getMin());
System.out.println("Sum of all numbers : " + stats.getSum());
System.out.println("Average of all numbers : " + stats.getAverage());
System.out.println("Random Numbers: ");
random.ints().limit(10).sorted().forEach(System.out::println);
count = strings.parallelStream().filter(string -> string.isEmpty()).count();
System.out.println("Empty Strings: " + count);
}
private static int getCountEmptyString(List<String> strings)
{
int count = 0;
for(String string: strings)
{
if(string.isEmpty()) { count++; }
}
return count;
}
private static int getCountLength(List<String> strings)
{
int count = 0;
for(String string: strings)
{
if(string.length() == 3) { count++; }
}
return count;
}
private static List<String> deleteEmptyStrings(List<String> strings)
{
List<String> filteredList = new ArrayList<String>();
for(String string: strings)
{
if(!string.isEmpty()) { filteredList.add(string); }
}
return filteredList;
}
private static String getMergedString(List<String> strings, String separator)
{
StringBuilder stringBuilder = new StringBuilder();
for(String string: strings)
{
if(!string.isEmpty())
{
stringBuilder.append(string);
stringBuilder.append(separator);
}
}
String mergedString = stringBuilder.toString();
return mergedString.substring(0, mergedString.length()-2);
}
private static List<Integer> getSquares(List<Integer> numbers)
{
List<Integer> squaresList = new ArrayList<Integer>();
for(Integer number: numbers)
{
Integer square = new Integer(number.intValue() * number.intValue());
if(!squaresList.contains(square)) { squaresList.add(square); }
}
return squaresList;
}
private static int getMax(List<Integer> numbers)
{
int max = numbers.get(0);
for(int i = 1;i < numbers.size();i++)
{
Integer number = numbers.get(i);
if(number.intValue() > max) { max = number.intValue(); }
}
return max;
}
private static int getMin(List<Integer> numbers)
{
int min = numbers.get(0);
for(int i= 1;i < numbers.size();i++)
{
Integer number = numbers.get(i);
if(number.intValue() < min) { min = number.intValue(); }
}
return min;
}
private static int getSum(List numbers)
{
int sum = (int)(numbers.get(0));
for(int i = 1;i < numbers.size();i++) { sum += (int)numbers.get(i); }
return sum;
}
private static int getAverage(List<Integer> numbers) { return getSum(numbers) / numbers.size(); }
}
Java - Stream API Improvements: Streams were introduced in Java to help developers perform aggregate
operations from a sequence of objects. With Java 9, few more methods are added to make streams better.
takeWhile(Predicate Interface) Method: Syntax: default Stream<T> takeWhile(Predicate<? super T>
predicate)
takeWhile method takes all the values until the predicate returns false. It returns, in case of ordered stream, a
stream consisting of the longest prefix of elements taken from this stream matching the given predicate.
import java.util.stream.Stream;
public class StreamDemo
{
public static void main(String[] args)
{
Stream.of("a","b","c","","e","f").takeWhile(s->!s.isEmpty()).forEach(System.out::print);

}
}
dropWhile(Predicate Interface): Syntax: default Stream<T> dropWhile(Predicate<? super T> predicate)
dropWhile method throw away all the values at the start until the predicate returns true. It returns, in case of
ordered stream, a stream consisting of the remaining elements of this stream after dropping the longest prefix
of elements matching the given predicate.
import java.util.stream.Stream;
public class StreamDemo
{
public static void main(String[] args)
{
Stream.of("a","b","c","","e","f").dropWhile(s-> !s.isEmpty()).forEach(System.out::print);
System.out.println();
Stream.of("a","b","c","","e","","f").dropWhile(s-> !s.isEmpty()).forEach(System.out::print);
}
}
iterate Method: Syntax: static <T> Stream<T> iterate(T seed, Predicate<? super T> hasNext,
UnaryOperator<T> next)
iterate method now has hasNext predicate as parameter which stops the loop once hasNext predicate returns
false.
import java.util.stream.IntStream;
public class StreamDemo
{
public static void main(String[] args)
{
IntStream.iterate(3, x -> x < 10, x -> x+ 3).forEach(System.out::println);
}
}
ofNullable: Syntax: static <T> Stream<T> ofNullable(T t)
ofNullable method is introduced to prevent NullPointerExceptions and to avoid null checks for streams. This
method returns a sequential Stream containing single element, if non-null, otherwise returns an empty Stream.
import java.util.stream.Stream;
public class StreamDemo
{
public static void main(String[] args)
{
long count = Stream.ofNullable(100).count();
System.out.println(count);
count = Stream.ofNullable(null).count();
System.out.println(count);
}
}
Output
1
0

Java Method References: Method is a collection of statements that perform some specific task and return
the result to the caller. A method reference in Java is the shorthand syntax for a lambda expression that
contains just one method call. In general, one doesn’t have to pass arguments to method references. Method
references were introduced in java 8. Method reference is a special type of lambda expression. It fulfils the
purpose of a lambda expression by increasing the readability and writing clean code. A method reference
works in case of functional interfaces. Wherever, we are calling a method of a functional interface with a
lambda expression, we can use method reference. Method references help to point to methods by their names
even without specifying the arguments. Arguments are passed by the lambda expression. A method reference
is described using "::" symbol.
Example:// Using Method Reference
import java.util.Arrays;
public class MethodRefDemo
{
public static void print(String s)
{
System.out.println(s);
}
public static void main(String[] args)
{
String[] names = {"Akash", "Vikash", "Manish"};
// Using method reference to print each name
Arrays.stream(names).forEach(MethodRefDemo::print);
}
}
Key Benefits of Method References:
 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 other 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

Refer to a method in an object Object :: methodName

Print all elements in a list list.forEach(s -> System.out.println(s));

Shorthand to print all elements in a list list.forEach(System.out::println);

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
Method Reference for Static Method: A static method can be referred using "::" symbol easily without
creating any instance of the class, and by using class name. Syntax: <<class-name>>::methodName
Example:
import java.util.ArrayList;
import java.util.List;
public class MethodRefDemo
{
public static void main(String args[])
{
List<String> names = new ArrayList<>();
names.add("NKM");
names.add("AMIT");
names.add("SACHIN");
// Approach 1: for loop to print all elements
for (String name: names) {System.out.println(name);}
// Approach 2: lambda expression to print the elements in for each loop
names.forEach(s->System.out.println(s));
// Approach 3: Method reference to print elements in for each loop
names.forEach(System.out::println);
}
}
Method Reference for Instance Method: Similar to static methods, we can refer to instance method of an
object using Method reference. Syntax: <<object-name>>::methodName
Example:
import java.util.Arrays;
import java.util.List;
public class MathRefDemo
{
public static void main(String args[])
{
List<Integer> numbers = Arrays.asList(10,5,40,19,82,67,43);
System.out.println("Sorted using lambda expression");
numbers = numbers.stream().sorted((a,b) -> a.compareTo(b)).toList();
System.out.println(numbers);
numbers = Arrays.asList(10,5,40,19,82,67,43);
System.out.println("Sorted using method reference" );
numbers = numbers.stream().sorted(Integer::compareTo).toList();
System.out.println(numbers);
}
}
Method Reference for Constructor: We can use method reference to invoke constructor as well. This is
similar to static method invocation but here we're using new instead of method name.
Syntax: <<class-name>>::new
Example:
import java.util.Arrays;
import java.util.List;
public class MethodRefDemo
{
public static void main(String args[])
{
List<String> studentNames = Arrays.asList("NKM","AMIT","SACHIN","VIKASH");
// Create the list of student objects from names
Student[] students = studentNames.stream().map(Student::new).toArray(Student[]::new);
List<String> list = Arrays.asList(students);
System.out.println(list);
}
}
class Student
{
String name;
Student(String name){this.name = name; }
public String toString(){return this.name;}
}

1. Reference to a 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 MethodRefDemo
{ // 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("Vikash", 26));
personList.add(new Person("Poonam", 29));
personList.add(new Person("Sachin", 28));
// Using static method reference to sort array by name
Collections.sort(personList, MethodRefDemo::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, MethodRefDemo::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);
}
}
2. Reference to an Instance Method of a Particular Object
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 MethodRefDemo
{
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("Vikash", 25));
personList.add(new Person("Poonam", 30));
personList.add(new Person("Sachin", 39));
// 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);
}
}
3. Reference to an Instance Method of an Arbitrary Object of a Particular Type
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 MethodRefDemo
{
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("Vikash");
personList.add("Akash");
personList.add("Manish");
// Method reference to String type
Collections.sort(personList, String::compareToIgnoreCase);
// Printing the elements(names) on console
personList.forEach(System.out::println);
}
}
4. Constructor Method Reference
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;
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 MethodRefDemo {
// 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);
}
}
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.

Base64 Encoding & Decoding: Base64 is a binary-to-text encoding scheme that represents binary data in an
ASCII string format. It is commonly used for encoding data that needs to be stored and transferred over media
that are designed to deal with text(like transporting images inside an XML, JSON document, etc.). Java
provides built-in support for Base64 encoding and decoding through the java.util.Base64 class, introduced in
Java 8.Base64 encoding allows encoding binary data as text strings for safe transport, at the cost of larger data
size.
Basic Encoding and Decoding: The java.util.Base64 class provides static methods to obtain encoders and
decoders for the Base64 encoding scheme. The java.util.Base64 class provides static methods to encode and
decode between binary and base64 formats. The encode() and decode() methods are used.
Syntax: String Base64format=Base64.getEncoder().encodeToString("String".getBytes());
The basic encoder encodes the input as-is, without any line separation. Here is an example of encoding and
decoding a simple string:
import java.util.Base64;
public class ExampleDemo
{
public static void main(String[] args)
{
String originalInput = "test input";
// Encoding
String encodedString = Base64.getEncoder().encodeToString(originalInput.getBytes());
System.out.println("Encoded String: " + encodedString);
// Decoding
byte[] decodedBytes = Base64.getDecoder().decode(encodedString);
String decodedString = new String(decodedBytes);
System.out.println("Decoded String: " + decodedString);
}
}
Explanation of the above Program:
 It imports the Base64 class for encoding and decoding.
 It encodes the input string to Base64 format and stores in a variable.
 It decodes the Base64 encoded string back to original string.
 It prints the original, encoded and decoded strings to verify it works.
 The Base64 class provides easy methods to encode and decode strings in this format.
URL and Filename Safe Encoding & Decoding: The URL encoding is the same as Basic encoding the only
difference is that it encodes or decodes the URL and Filename safe Base64 alphabet and does not add any line
separation.
URL Encoding Syntax: String encodedURL = Base64.getUrlEncoder()
.encodeToString(actualURL_String.getBytes());
URL Decoding Syntax: byte[] decodedURLBytes = Base64.getUrlDecoder().decode(encodedURLString);
String actualURL= new String(decodedURLBytes);
Explanation: In above code we called Base64.Decoder using getUrlDecoder() and then decoded the URL
string passed in decode() method as parameter then convert return value to actual URL.The URL and Filename
safe encoder uses a modified Base64 alphabet that is safe for use in URLs and filenames. It does not add any
line separation. Here is an example:
import java.util.Base64;
public class UrlExample
{
public static void main(String[] args)
{
String originalUrl = "https://www.example.com/?query=java";
// Encoding
String encodedUrl = Base64.getUrlEncoder().encodeToString(originalUrl.getBytes());
System.out.println("Encoded URL: " + encodedUrl);
// Decoding
byte[] decodedBytes = Base64.getUrlDecoder().decode(encodedUrl);
String decodedUrl = new String(decodedBytes);
System.out.println("Decoded URL: " + decodedUrl);
}
}
MIME Encoding: The MIME encoder generates a Base64-encoded output that is MIME-friendly. Each line
of the output is no longer than 76 characters and ends with a carriage return followed by a linefeed (\r\n).
import java.util.Base64;
import java.util.UUID;
public class MimeExample
{
public static void main(String[] args)
{
StringBuilder buffer = new StringBuilder();
for (int count = 0; count < 10; ++count)
buffer.append(UUID.randomUUID().toString());
byte[] encodedAsBytes = buffer.toString().getBytes();
// Encoding
String encodedMime = Base64.getMimeEncoder().encodeToString(encodedAsBytes);
System.out.println("Encoded MIME: " + encodedMime);
// Decoding
byte[] decodedBytes = Base64.getMimeDecoder().decode(encodedMime);
String decodedMime = new String(decodedBytes);
System.out.println("Decoded MIME: " + decodedMime);
}
}
Important Considerations: When using Base64 encoding and decoding in Java, it is important to handle
potential exceptions such as IllegalArgumentException for invalid input. Additionally, be mindful of the
padding characters (=) added by the encoder to ensure the output length is a multiple of four. These padding
characters are discarded during decoding.
Example: // Java program to demonstrate Decoding Basic Base 64 format to String
import java.util.*;
public class CodeDemo {
public static void main(String[] args)
{
// create a encoded URL to decode
String encoded = "aHR0cHM6Ly93d3cuZ2Vla3Nmb3JnZWVrcy5vcmcv";
// print encoded URL
System.out.println("encoded URL:\n"+ encoded);
// decode into String URL from encoded format
byte[] actualByte = Base64.getUrlDecoder().decode(encoded);
String actualURLString = new String(actualByte);
// print actual String
System.out.println("actual String:\n"+ actualURLString);
}
}
Example(Base):
import java.io.UnsupportedEncodingException;
import java.util.Base64;
public class Base64Tester
{
public static void main(String[] args) throws UnsupportedEncodingException
{
String stringToEncode = "HCST College";
// Encode using basic encoder
String base64encodedString =
Base64.getEncoder().encodeToString(stringToEncode.getBytes("utf-8"));
System.out.println("Encoded String: " + base64encodedString);
// Decode the base64 encoded string using basic decoder
byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString);
// print the decoded string
System.out.println("Decoded String: " + new String(base64decodedBytes, "utf-8"));
}
}
Example(URL):
import java.io.UnsupportedEncodingException;
import java.util.Base64;
public class BaseExample
{
public static void main(String[] args) throws UnsupportedEncodingException
{
String stringToEncode = "HCST College Students";
// Encode using url encoder
String base64encodedString =
Base64.getUrlEncoder().encodeToString(stringToEncode.getBytes("utf-8"));
System.out.println("Encoded String: " + base64encodedString);
// Decode the base64 encoded string using url decoder
byte[] base64decodedBytes = Base64.getUrlDecoder().decode(base64encodedString);
// print the decoded string
System.out.println("Decoded String: " + new String(base64decodedBytes, "utf-8"));
}
}
Example(MIME):
import java.io.UnsupportedEncodingException;
import java.util.Base64;
import java.util.UUID;
public class MimeExampleDemo
{
public static void main(String[] args) throws UnsupportedEncodingException
{
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 10; ++i)
{
stringBuilder.append(UUID.randomUUID().toString());
stringBuilder.append(",");
}
byte[] mimeBytes = stringBuilder.toString().getBytes("utf-8");
String mimeEncodedString = Base64.getMimeEncoder().encodeToString(mimeBytes);
System.out.println("Base64 Encoded String (MIME) : " + mimeEncodedString);
// Decode the base64 encoded string using url decoder
byte[] base64decodedBytes = Base64.getMimeDecoder().decode(mimeEncodedString);
// print the decoded string
System.out.println("Decoded String: " + new String(base64decodedBytes, "utf-8"));
}
}
Class Base64(public class Base64 extends Object): This class consists exclusively of static methods for
obtaining encoders and decoders for the Base64 encoding scheme. The implementation of this class supports
the following types of Base64 as specified in RFC 4648 and RFC 2045.
 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 and Filename safe: Output is mapped to set of characters lying in A-Za-z0-9+_. Output is URL
and filename safe. 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. All line separators or other
characters not found in the base64 alphabet table are ignored in decoding operation. Unless otherwise
noted, passing a null argument to a method of this class will cause a NullPointerException to be
thrown.
Nested Classes
Modifier and Class Description
Type
static class Base64.Decoder This class implements a decoder for decoding byte data using the Base64 encoding
as specified in RFC 4648 and RFC 2045.
static class Base64.Encoder This class implements an encoder for encoding byte data using the Base64 encoding
as specified in RFC 4648 and RFC 2045.
Method Summary:

Modifier and Type Method Description


static Base64.Decoder getDecoder() Returns a Base64.Decoder that decodes using the Bas
base64 encoding scheme.
static Base64.Encoder getEncoder() Returns a Base64.Encoder that encodes using the Bas
base64 encoding scheme.
static Base64.Decoder getMimeDecoder() Returns a Base64.Decoder that decodes using the MIM
base64 decoding scheme.
static Base64.Encoder getMimeEncoder() Returns a Base64.Encoder that encodes using the MIM
base64 encoding scheme.
static Base64.Encoder getMimeEncoder(int lineLength, Returns a Base64.Encoder that encodes using the MIM
byte[] lineSeparator) base64 encoding scheme with specified line length an
separators.
static Base64.Decoder getUrlDecoder() Returns a Base64.Decoder that decodes using the UR
Filename safe type base64 encoding scheme.
static Base64.Encoder getUrlEncoder() Returns a Base64.Encoder that encodes using the UR
Filename safe type base64 encoding scheme.
Methods inherited from class java.lang.Object:
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
Method Detail:
 getEncoder(): public static Base64.Encoder getEncoder(); Returns a Base64.Encoder that encodes using
the Basic type base64 encoding scheme.
 getUrlEncoder(): public static Base64.Encoder getUrlEncoder();Returns a Base64.Encoder that encodes
using the URL and Filename safe type base64 encoding scheme.
 getMimeEncoder(): public static Base64.Encoder getMimeEncoder();Returns a Base64.Encoder that
encodes using the MIME type base64 encoding scheme.
 getMimeEncoder():public
static Base64.Encoder getMimeEncoder(int lineLength,byte[] lineSeparator);Returns
a Base64.Encoder that encodes using the MIME type base64 encoding scheme with specified line length
and line separators.
 Parameters: lineLength -the length of each output line (rounded down to nearest multiple of 4).
If lineLength <= 0 the output will not be separated in lines lineSeparator - the line separator for each output
line.
 Throws:IllegalArgumentException - if lineSeparator includes any character of "The Base64 Alphabet" as
specified in Table 1 of RFC 2045.
 getDecoder(): public static Base64.Decoder getDecoder(); Returns a Base64.Decoder that decodes using
the Basic type base64 encoding scheme.
 getUrlDecoder(): public static Base64.Decoder getUrlDecoder(); Returns a Base64.Decoder that decodes
using the URL and Filename safe type base64 encoding scheme.
 getMimeDecoder(): public static Base64.Decoder getMimeDecoder(); Returns a Base64.Decoder that
decodes using the MIME type base64 decoding scheme.

Automatic Resource Management in Java (try with resource statements ): In Java, the Try-with-
resources statement is a try statement that declares one or more resources in it. A resource is an object that
must be closed once your program is done using it. For example, a File resource or a Socket connection
resource. The try-with-resources statement ensures that each resource is closed at the end of the statement
execution. If we don’t close the resources, it may constitute a resource leak and also the program could
exhaust the resources available to it. You can pass any object as a resource that
implements java.lang.AutoCloseable, which includes all objects which implement java.io.Closeable. By
this, now we don’t need to add an extra finally block for just passing the closing statements of the resources.
The resources will be closed as soon as the try-catch block is executed.
This feature makes the code more robust and cuts down the lines of code. This feature is known as Automatic
Resource Management(ARM) using try-with-resources from Java 7 onwards. The try-with-resources
statement is a try statement that declares one or more resources. This statement ensures that each resource is
closed at the end of the statement, which eases working with external resources that need to be disposed off
or closed in case of errors or successful completion of a code block.
Resource is an object that must be closed after the program is finished using it. Any object that
implements java.lang.AutoCloseable, which includes all objects which implement java.io.Closeable, can be
used as a resource.
Syntax: Try-with-resources
try(declare resources here) {
// use resources
}
catch(FileNotFoundException e) {
// exception handling
}
The old way – without using try-with-resources
Example: //Reading file passed as an argument at command prompt
import java.io.*;
class ShowFile
{
public static void main(String arg[]){
int i;
FileInputStream fin=null;
if(arg.length!=1)
{
System.out.println("Specify filename in command prompt:>");
return;
}
try{fin= new FileInputStream(arg[0]);}
catch(FileNotFoundException e)
{
System.out.println("Can not open file"+e);
return;
}
try
{
do
{
i=fin.read();
if(i!=-1)
System.out.print((char)i);
}while(i!=-1);
}
catch(IOException e){ System.out.println("Error reading file:>"+e);}
try{ fin.close(); }
catch(IOException e){System.out.println("Error closing file"+e);}
}
}
Example: //Reading file passed as an argument at command prompt using finally
import java.io.*;
class ShowFile
{
public static void main(String arg[]){
int i;
FileInputStream fin=null;
if(arg.length!=1)
{
System.out.println("Specify filename in command prompt:>");
return;
}
try{fin= new FileInputStream(arg[0]);}
catch(FileNotFoundException e)
{
System.out.println("Can not open file"+e);
return;
}
try
{
do
{
i=fin.read();
if(i!=-1)
System.out.print((char)i);
}while(i!=-1);
}
catch(IOException e){ System.out.println("Error reading file:>"+e);}
finally{try{ if(fin!=null) fin.close(); }
catch(IOException e){System.out.println("Error closing file"+e);}}
}
}
Example:// Java program to illustrate cleaning of resources
import java.util.*;
import java.io.*;
class Resource
{
public static void main(String args[])
{
BufferedReader br = null;
String str = " ";
System.out.println("Enter the file path:>");
br = new BufferedReader(new InputStreamReader(System.in));
try{ str = br.readLine(); }
catch(IOException e){ e.printStackTrace(); }
try{
String s;
br = new BufferedReader(new FileReader(str));
while ((s = br.readLine()) != null)
System.out.println(s);
}
catch (IOException e){ e.printStackTrace(); }
finally
{
try
{
if (br != null)
br.close();
}
catch (IOException ex) { ex.printStackTrace(); }
}
}
}
Using try-with-resources: In the try-with-resources method, there is no use of the finally block. The file
resource is opened in try block inside small brackets. Only the objects of those classes can be opened within
the block which implements the AutoCloseable interface, and those objects should also be local. The resource
will be closed automatically regardless of whether the try statement completes normally or abruptly.
static String readFirstLineFromFile(String path) throws IOException
{
try (BufferedReader br = new BufferedReader(new FileReader(path)))
{
return br.readLine();
}
}
Example: //Reading file passed as an argument at command prompt using try-with-resources
import java.io.*;
class ShowFile
{
public static void main(String arg[]){
int i;
if(arg.length!=1)
{
System.out.println("Specify filename in command prompt:>");
return;
}
try(FileInputStream fin= new FileInputStream(arg[0]))
{
do
{
i=fin.read();
if(i!=-1)
System.out.print((char)i);
}while(i!=-1);
}
catch(FileNotFoundException e){System.out.println("Can not open file"+e);}
catch(IOException e){ System.out.println("Error reading file:>"+e);}
}
}
Example:// Java program to illustrate Automatic Resource Management in Java without finally block
import java.io.*;
import java.util.*;
class Resource {
public static void main(String args[])
{
String str = "";
BufferedReader br = null;
System.out.println("Enter the file path:>");
br = new BufferedReader(new InputStreamReader(System.in));
try { str = br.readLine(); }
catch (IOException e) { e.printStackTrace(); }
try (BufferedReader b= new BufferedReader(new FileReader(str))) {
String s;
while ((s = b.readLine()) != null)
System.out.println(s);
}
catch (IOException e) {e.printStackTrace(); }
}
}
Example: try-with-resources having a single resource
import java.io.*;
class Resource {
public static void main(String[] args)
{
try (FileOutputStream fos= new FileOutputStream("test.txt")) {
String text= "Hello World. This is my java program using try with resources";
byte arr[] = text.getBytes();
fos.write(arr);
}
catch (Exception e) {System.out.println(e); }
System.out.println("Resource are closed and message has been written into the test.txt");
}
}
Automatic Resource Management in multiple resources: Multiple resources can be used inside a try-with-
resources block and have them all automatically closed. In this case, the resources will be closed in the reverse
order in which they were created inside the brackets.
Example: //Program using multiple resources without try-with-resources
import java.io.*;
class CopyFile
{
public static void main(String args[])
{
int i;
FileInputStream fin=null;
FileOutputStream fout=null;
if(args.length!=2)
{
System.out.println("Please enter source and target file names:>");
return;
}
try
{
fin=new FileInputStream(args[0]);
fout=new FileOutputStream(args[1]);
do
{
i=fin.read();
if(i!=-1) fout.write(i);
}while(i!=-1);
}
catch(IOException e){System.out.println("IO Error:>"+e);}
finally
{
try{ if(fin!=null) fin.close();}
catch(IOException e){System.out.println("Err closing I file:>"+e);}
try{ if(fout!=null) fout.close();}
catch(IOException e){System.out.println("Err closing O file:>"+e);}

}
}
}
Example: //Program using multiple resources with try-with-resources
import java.io.*;
class CopyFile
{
public static void main(String args[])
{
int i;
if(args.length!=2)
{
System.out.println("Please enter source and target file names:>");
return;
}
try(FileInputStream fin= new FileInputStream(args[0]);FileOutputStream fout= new
FileOutputStream(args[1]))
{
do
{
i=fin.read();
if(i!=-1) fout.write(i);
}while(i!=-1);
}
catch(IOException e){System.out.println("IO Error:>"+e);}
}
}
Example: try-with-resources having multiple resources
// Java program for try-with-resources having multiple resources
import java.io.*;
class Resource {
public static void main(String[] args)
{
try (FileOutputStream fos= new FileOutputStream("outputfile.txt");BufferedReader br = new
BufferedReader(new FileReader("test.txt"))) {
String text;
while ((text = br.readLine()) != null) {
byte arr[] = text.getBytes();
fos.write(arr);
}
System.out.println("File content copied to another one.");
}
catch (Exception e) {System.out.println(e); }
System.out.println("Resource are closed and message has been written into the test.txt");
}
}
Example: // Java program to illustrate Automatic Resource Management in Java with multiple resource
class Resource
{
public static void main(String s[])
{
try (Demo d = new Demo(); Demo1 d1 = new Demo1())
{
int x = 10 / 0;
d.show();
d1.show1();
}
catch (ArithmeticException e) { System.out.println(e); }
}
}
class Demo implements AutoCloseable
{
void show() { System.out.println("inside show"); }
public void close() { System.out.println("close from demo"); }
}
class Demo1 implements AutoCloseable
{
void show1() { System.out.println("inside show1"); }
public void close(){ System.out.println("close from demo1"); }
}
Note: In the above example, Demo and Demo1 are the custom resources managed inside the try block. Such
resources need to implement the AutoCloseable interface. When we open any such AutoCloseable resource
in the special try-with-resource block, immediately after finishing the try block, JVM calls this.close() method
on all resources initialized in the try block.
Important Points:
1. The finally blocks were used to clean up the resources before Java 7.
2. After java 7, resource cleanup is done automatically.
3. ARM is done when you initialize resources in the try-with-resources block because of the interface
AutoCloseable. Its close method is invoked by JVM as soon as the try block finishes.
4. Calling the close() method might lead to unexpected results.
5. A resource that we use in try-with-resource must be subtypes of AutoCloseable to avoid a compile-time
error.
6. The resources which are used in multiple resource ARM must be closed in reverse order as given in the
above example
Exceptions in try-with-resources: When it comes to exceptions, there is a difference in try-catch-finally
block and try-with-resources block. If an exception is thrown in both try block and finally block, the method
returns the exception thrown in finally block.
For try-with-resources, if an exception is thrown in a try block and in a try-with-resources statement, then
the method returns the exception thrown in the try block. The exceptions thrown by try-with-resources are
suppressed, i.e. we can say that try-with-resources block throws suppressed exceptions.
Try with Resources Improvements: In this example, we'll use BufferedReader as resource to read a string
and then BufferedReader is to be closed.
Java 9 onwards
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
public class DemoTester {
public static void main(String[] args) throws IOException {
System.out.println(readData("test"));
}
static String readData(String message) throws IOException {
Reader inputString = new StringReader(message);
BufferedReader br = new BufferedReader(inputString);
try (br) {
return br.readLine();
}
}
}

Annotations in Java: Annotations, a form of metadata, provide data about a program that is not part of the
program itself. Annotations have no direct effect on the operation of the code they annotate. Annotations have
a number of uses, among them:
 Information for the compiler — Annotations can be used by the compiler to detect errors or suppress
warnings.
 Compile-time and deployment-time processing — Software tools can process annotation
information to generate code, XML files, and so forth.
 Runtime processing — Some annotations are available to be examined at runtime.
Annotations are used to provide supplemental information about a program.
 Annotations start with ‘@’.
 Annotations do not change the action of a compiled program.
 Annotations help to associate metadata (information) to the program elements i.e. instance variables,
constructors, methods, classes, etc.
 Annotations are not pure comments as they can change the way a program is treated by the compiler.
 Annotations basically are used to provide additional information, so could be an alternative to XML
and Java marker interfaces.
Hierarchy of Annotations in Java:
Example:This program throws compiler error because we have mentioned override, but not overridden, we
have overloaded display.
class Base {
public void display(){ System.out.println("Base display()"); }
}
class Derived extends Base {
// Overriding method as already up in above class
@Override public void display(int x)//display is overloaded instead of overriding
{
System.out.println("Derived display(int )");
}
public static void main(String args[])
{
Derived obj = new Derived();
obj.display();
}
}
Categories of Annotations: There are broadly 5 categories of annotations as listed below:
1. Marker Annotations
2. Single value Annotations
3. Full Annotations
4. Type Annotations
5. Repeating Annotations
Category 1: Marker Annotations: The only purpose is to mark a declaration. These annotations contain
no members and do not consist of any data. Thus, its presence as an annotation is sufficient. Since the marker
interface contains no members, simply determining whether it is present or absent is
sufficient. @Override is an example of Marker Annotation. Example: @TestAnnotation()
Example:// Java Program illustrating declaration of custom Marker Annotation
import java.io.*;
// Sample for marker Annotation:Custom annotation declaration
@interface books_data
{
// No variable declared here
}
class Books {
public static void main(String[] args)
{
// Print statement
System.out.println("Example of Marker Annotations.");
}
}
Since no variable is declared inside this annotation, this will be known as Marker Annotation.
Category 2: Single value Annotations: These annotations contain only one member and allow a shorthand
form of specifying the value of the member. We only need to specify the value for that member when the
annotation is applied and don’t need to specify the name of the member. However, in order to use this
shorthand, the name of the member must be a value. Example: @TestAnnotation(“testing”);
Example :// Java Program Illustrating Declaration of Custom Single Value Annotation
import java.io.*;
// Sample for single value Annotation:Custom annotation declaration
@interface books_data
{
// Single variable declaration
String book_name();
}
class Books {
public static void main(String[] args)
{
System.out.println("Example of single value Annotation.");
}
}
We can see that we have declared a variable book_name of type String, and it's the only variable declared
inside the Annotation, hence, this is an example of Single-Value Annotation.
Category 3: Full Annotations: These annotations consist of multiple data members, names, values,
pairs. Example: @TestAnnotation(owner=”Rahul”, value=”B Tech CSE”)
Example 3: // Java Program illustrating declaration of multi value Annotation
import java.io.*;
// Sample for multi value annotation:Custom annotation declaration
@interface books_data
{
// Multiple variable declarations
String book_name();
int book_price();
String author();
}
class Books {
public static void main(String[] args)
{
System.out.println("Example of multi value Annotation.");
}
}
We have declared multiple variables inside our Annotation, this is an example of Multi-value Annotation.
Category 4: Type Annotations: These annotations can be applied to any place where a type is being used.
For example, we can annotate the return type of a method. These are annotated with @Target annotation.
Example: // Java Program to Demonstrate Type Annotation
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
// Using target annotation to annotate a type
@Target(ElementType.TYPE_USE)
// Declaring a simple type annotation
@interface TypeAnnoDemo{}
public class TypeAnnotation {
public static void main(String[] args) {
// Annotating the type of a string
@TypeAnnoDemo String string = "I am annotated with a type annotation";
System.out.println(string);
abc();
}
// Annotating return type of a function
static @TypeAnnoDemo int abc() {
System.out.println("This function's return type is annotated");
return 0;
}
}
Category 5: Repeating Annotations: These are the annotations that can be applied to a single item more
than once. For an annotation to be repeatable it must be annotated with the @Repeatable annotation, which
is defined in the java.lang.annotation package. Its value field specifies the container type for the repeatable
annotation. The container is specified as an annotation whose value field is an array of the repeatable
annotation type. Hence, to create a repeatable annotation, firstly the container annotation is created, and
then the annotation type is specified as an argument to the @Repeatable annotation.
Example:// Java Program to Demonstrate a Repeatable Annotation
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
// Make Words annotation repeatable
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyRepeatedAnnos.class)
@interface Words
{
String word() default "Hello";
int value() default 0;
}
// Create container annotation
@Retention(RetentionPolicy.RUNTIME)
@interface MyRepeatedAnnos
{
Words[] value();
}
public class Main {
// Repeat Words on newMethod
@Words(word = "First", value = 1)
@Words(word = "Second", value = 2)
public static void newMethod()
{
Main obj = new Main();
try {
Class<?> c = obj.getClass();
// Obtain the annotation for newMethod
Method m = c.getMethod("newMethod");
// Display the repeated annotation
Annotation anno= m.getAnnotation(MyRepeatedAnnos.class);
System.out.println(anno);
}
catch (NoSuchMethodException e) { System.out.println(e); }
}
public static void main(String[] args) { newMethod(); }
}
Output: @MyRepeatedAnnos(value={@Words(value=1, word="First"), @Words(value=2,
word="Second")})
Predefined/ Standard Annotations: Java popularly defines seven built-in annotations as we have seen up
in the hierarchy diagram.
 Four are imported from java.lang.annotation: @Retention, @Documented, @Target,
and @Inherited.
 Three are included in java.lang: @Deprecated, @Override and @SuppressWarnings
Annotation 1: @Deprecated
 It is a marker annotation. It indicates that a declaration is obsolete and has been replaced by a newer
form.
 The Javadoc @deprecated tag should be used when an element has been deprecated.
 @deprecated tag is for documentation and @Deprecated annotation is for runtime reflection.
 @deprecated tag has higher priority than @Deprecated annotation when both are together used.
public class DeprecatedTest
{
@Deprecated
public void Display()
{
System.out.println("Deprecatedtest display()");
}
public static void main(String args[])
{
DeprecatedTest d1 = new DeprecatedTest();
d1.Display();
}
}
Output: Deprecatedtest display()
Annotation 2: @Override: It is a marker annotation that can be used only on methods. A method annotated
with @Override must override a method from a superclass. If it doesn’t, a compile-time error will result.
It is used to ensure that a superclass method is actually overridden, and not simply overloaded.
Example: // Java Program to Illustrate Override Annotation
class Base
{
public void display()
{
System.out.println("Base display()");
}
public static void main(String args[])
{
Base t1 = new Derived();
t1.Display();
}
}
// Extending above class
class Derived extends Base
{
@Override
public void display()
{
System.out.println("Derived display()");
}
}
Output: Derived display()
Annotation 3: @SuppressWarnings: It is used to inform the compiler to suppress specified compiler
warnings. The warnings to suppress are specified by name, in string form. This type of annotation can be
applied to any type of declaration. Java groups warnings under two categories. They
are deprecated and unchecked. Any unchecked warning is generated when a legacy code interfaces with a
code that uses generics.
Example:// Java Program to illustrate SuppressWarnings Annotation
class DeprecatedTest
{
@Deprecated
public void Display()
{
System.out.println("DeprecatedTest display()");
}
}
public class SuppressWarningTest
{
// If we comment below annotation, program generates warning
@SuppressWarnings({"checked", "deprecation"})
public static void main(String args[])
{
DeprecatedTest d1 = new DeprecatedTest();
d1.Display();
}
}
Output: Deprecatedtest display()
Annotation 4: @Documented: It is a marker interface that tells a tool that an annotation is to be
documented. Annotations are not included in 'Javadoc' comments. The use of @Documented annotation in
the code enables tools like Javadoc to process it and include the annotation type information in the generated
document.
Annotation 5: @Target: It is designed to be used only as an annotation to another
annotation. @Target takes one argument, which must be constant from the ElementType enumeration.
This argument specifies the type of declarations to which the annotation can be applied. The constants are
shown below along with the type of the declaration to which they correspond.
Target Constant Annotations Can Be Applied To

ANNOTATION_TYPE Another annotation

CONSTRUCTOR Constructor

FIELD Field

LOCAL_VARIABLE Local variable

METHOD Method

PACKAGE Package

PARAMETER Parameter

TYPE Class, Interface, or enumeration

We can specify one or more of these values in a @Target annotation. To specify multiple values, we must
specify them within a braces-delimited list. For example, to specify that an annotation applies only to fields
and local variables, you can use this @Target annotation: @Target({ElementType.FIELD,
ElementType.LOCAL_VARIABLE}) @Retention Annotation It determines where and how long the
annotation is retent. The 3 values that the @Retention annotation can have:
 SOURCE: Annotations will be retained at the source level and ignored by the compiler.
 CLASS: Annotations will be retained at compile-time and ignored by the JVM.
 RUNTIME: These will be retained at runtime.
Annotation 6: @Inherited: @Inherited is a marker annotation that can be used only on annotation
declaration. It affects only annotations that will be used on class declarations. @Inherited causes the
annotation for a superclass to be inherited by a subclass. Therefore, when a request for a specific annotation
is made to the subclass, if that annotation is not present in the subclass, then its superclass is checked. If
that annotation is present in the superclass, and if it is annotated with @Inherited, then that annotation will
be returned.
Annotation 7: User-defined (Custom): User-defined annotations can be used to annotate program
elements, i.e. variables, constructors, methods, etc. These annotations can be applied just before the
declaration of an element (constructor, method, classes, etc).
Syntax: Declaration
[Access Specifier] @interface<AnnotationName>
{
DataType <Method Name>() [default value];
}

Inherited Annotations in Java: Java, by default, does not allow the custom annotations to be
inherited. @inherited is a type of meta-annotation used to annotate custom annotations so that the subclass
can inherit those custom annotations. The syntax to use @inherited annotation is mentioned below.
@Inherited
@interface CustomAnnotation {
String value () default "BTech";
}
There are two scenarios:
 Case 1: Using @Inherited annotation
 Case 2: Without using @inherited Annotation
Case 1: Using @Inherited annotation: In the code example shown below, we have created a custom
annotation named CustomAnnotation and annotated it with @Inherited annotation. We used the
CustomAnnotation to annotate the Super class which is Inherited by InheritedAnnotationDemo class.
InheritedAnnotationDemo also inherits the CustomAnnotation as it is annotated using @Inherited
annotation.
Example: // Java Program to Illustrating Use of Custom Annotations with @inherited annotation
import java.lang.annotation.*;
import java.lang.reflect.AnnotatedElement;
// Creating our single valued custom annotation with @inherited annotation
@Inherited
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
// Custom annotation
@interface CustomAnnotation
{
String value() default "BTech";
}
// Annotating the super class using the custom annotation
@CustomAnnotation(value = "Sky is limitless")
class Super {
}
// This is our derived class which inherits the Super class and the custom annotation
public class InheritedAnnotationDemo extends Super {
public static void main(String[] arg) throws Exception
{
// Printing the annotation used to annotated the Super and InheritedAnnotationDemo classes
System.out.println(new InheritedAnnotationDemo().getClass().getAnnotation(CustomAnnotation.class));
System.out.println(new Super().getClass().getAnnotation(CustomAnnotation.class));
// Obtaining the class class name and printing the annotation info about the annotation info attached to the
Super class
Class obj = Super.class;
// Calling the method 2 to print the annotation states
printAnnotationState(obj);
}
// Method 2 - To print the annotation state
static void printAnnotationState(AnnotatedElement ann)
{
// Obtaining all the annotations attached to the passed element and storing it in an array
Annotation[] annotationsArray= ann.getAnnotations();
// Iterating on all the annotations stored inside of the array above and printing their information
for (Annotation annotation : annotationsArray) {
// Print and display nameand value of annotation
System.out.println("Name of the annotation : "+ annotation.annotationType());
System.out.println("Value : " + ((CustomAnnotation)annotation).value());
}
}
}
Output: @CustomAnnotation(value="Sky is limitless")
@CustomAnnotation(value="Sky is limitless")
Name of the annotation : interface CustomAnnotation
Value : Sky is limitless
Case 2: Without using @inherited Annotation: In the code example mentioned below everything the
same except that we haven't used @Inherited annotation to annotate our CustomAnnotation. Due to this, the
CustomAnnotation is not inherited by the InheritedAnnotationDemo class. So, when we use getAnnotation()
method for InheritedAnnotationDemo class it does not return CustomAnnotation. In this case, where we
haven't used any annotation to annotate InheritedAnnotationDemo class it simply returns null.
Example :// Java Program to Illustrating Use of Custom Annotations without @inherited annotation
import java.lang.annotation.*;
import java.lang.reflect.AnnotatedElement;
// Creating our single valued custom annotation without @inherited annotation
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
// Custom annotation
@interface CustomAnnotation
{
String value() default "BTech";
}
// Annotating the super class using our custom annotation
@CustomAnnotation(value = "Sky is limitless")
class Super {
}
public class InheritedAnnotationDemo extends Super {
public static void main(String[] arg) throws Exception
{
// Printing the annotation used to annotated the Super and InheritedAnnotationDemo classes
System.out.println(new InheritedAnnotationDemo().getClass()
.getAnnotation(CustomAnnotation.class));
// As we haven't used the @Inherited Annotation to create the custom annotation therefore not
// inherited by InheritedAnnotationDemo class. When we use getAnnotation() now, returns null.
System.out.println(new Super().getClass().getAnnotation(CustomAnnotation.class));
// Obtaining the class class name and printing the annotation info about the annotation info attached to the
Super class
Class obj = Super.class;
// Calling the Method 2 to print the annotation state
printAnnotationState(obj);
}
// Method 2 To print the annotation state
static void printAnnotationState(AnnotatedElement ann)
{
// Obtaining all the annotations attached to the passed element and storing it in an array
Annotation[] annotationsArray= ann.getAnnotations();
// Iterating on all the annotations stored inside of the array above and printing their information
for (Annotation annotation : annotationsArray) {
// Print and display name and value of the annotation
System.out.println("Name of the annotation : "+ annotation.annotationType());
System.out.println("Value : "+ ((CustomAnnotation)annotation).value());
}
}
}
Output: null
@CustomAnnotation(value="Sky is limitless")
Name of the annotation : interface CustomAnnotation
Value : Sky is limitless
How to Create Your Own Annotations in Java? Annotations are a form of metadata that provide
information about the program but are not a part of the program itself. An Annotation does not affect the
operation of the code they Annotate. Now let us go through different types of java annotations present that
are listed as follows:
1. Predefined annotations.: @Deprecated, @Override, @SuppressWarnings, @SafeVarargs,
@FunctionalInterface.
2. Meta-annotations: @Retention, @Documented, @Target, @Inherited, @Repeatable.
3. Custom annotations: These are defined by the user. (We will be learning to create custom annotations in
this module).
We can create our own java annotations by referring to simple steps sequentially as follows:
1. To create your own Java Annotation you must use @interface Annotation_name, this will create a new
Java Annotation for you.
2. The @interface will describe the new annotation type declaration.
3. After giving a name to your Annotation, you will need to create a block of statements inside which you
may declare some variables.
The following rules for custom annotations need to be considered before implementing user-defined
annotations.
1. AnnotationName is an interface.
2. The parameter should not be associated with method declarations and throws clause should not be used
with method declaration.
3. Parameters will not have a null value but can have a default value.
4. default value is optional.
5. The return type of method should be either primitive, enum, string, class name, or array of primitive,
enum, string, or class name type.
There are three forms of annotations that can be defined in java as follows:
1. Marker Annotations: These are the annotations inside which no variables are declared or defined.
2. Single-value Annotations: These are the annotations inside which only a single variable is declared or
defined.
3. Multi-value Annotations: These are the annotations inside which multiple variables of multiple types can
be declared and defined.
Example:// Java Program to Demonstrate User-defined Annotations
package source;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
// User-defined annotation
@Documented
@Retention(RetentionPolicy.RUNTIME)
@ interface TestAnnotation
{
String Developer() default "Rahul";
String Expirydate();
} // will be retained at runtime
// Driver class that uses @TestAnnotation
public class Test
{
@TestAnnotation(Developer="Rahul", Expirydate="01-10-2020")
void fun1()
{
System.out.println("Test method 1");
}
@TestAnnotation(Developer="Anil", Expirydate="01-10-2021")
void fun2()
{
System.out.println("Test method 2");
}
public static void main(String args[])
{
System.out.println("Hello");
}
}
Output: Hello
Example: Let us take an example of a custom annotation called books_data to understand how different
forms of annotations are declared.
@interface books_data //using the syntax : @interface Annotation_name, we declared a new annotation here.
{ //beginning of annotation declaration and definition
/*Defining variables inside an annotation is optional. The number of variables declared inside an annotation
will describe its form.*/
} //end of annotation declaration and definition
Now let us see how to use custom annotations for which let us look at how you can use your custom
annotation:
 Method 1: Default annotations
 Method 2: Custom annotations
Focusing onto custom annotations for which in order to use your custom annotation, we simply need to call
your annotation using your annotation Name preceded with @symbol and pass the value of the declared
variables in an ordered manner to the variables that you have declared in the annotation.
Example 1:// Java Program illustrating Use of Custom Annotation
import java.io.*;
// Sample for marker annotation:Custom annotation declaration
@interface books_data
{
// Multiple variable declaration
String book_name();
int book_price();
String author();
}
// Using the custom Annotation
@books_data(book_name = "Effective Java", book_price = 30, author = "Joshua Bloch")
class BookStore {
}
class Books {
// Main driver method
public static void main(String[] args)
{
// Print statement
System.out.println("How to use the annotations?");
}
}
Output: How to use the annotations?
However, if you don't want to specify values when you are using the Annotation you can initialize the values
inside your Annotation using the default value. Using default value is optional.
Example 2:// Java Program Illustrating Default Values Declaration of Variables Inside an Annotation
import java.io.*;
// Sample for Marker annotation
@interface books_data
{
// Custom annotation declaration
String book_name() default "Effective Java";
// Declaring the default values
int book_price() default 30;
String author() default "Joshua Bloch";
// Multiple variable declaration
}
// Using the custom Annotation
@books_data
// Class 1
class BookStore {
}
// Class 2
class books {
// Main driver method
public static void main(String[] args)
{
// Print statement
System.out.println("Annotation using default values");
}
}
Output: Annotation using default values
If you still initialize the values when you are using your annotation when you have declared the default values,
the initialized value will overwrite the default values.
Module System: In Java 9, Module system was introduced to enhance Java code modularity. Module is an
abstraction over package. This module system is also known as JPMS, Java Platform Module System. It is
mostly referred as Modules.
What is a Module? A module is a self-describing collection of code and data and has a name to identify it. It
a new kind of programming component which can contains packages, configurations, resources specific to a
particular functionality. A module is able to restrict access to packages it contains. By default, code in a
package within a module is not visible to outside world, not even via reflection. Java platform is itself
modularised from java 9 onwards. If we use the list-modules command to list the java modules, it will print
the various modules java supports as following:
C:\Users\nkm>java --list-modules
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
Here we can see, that jdk specific packages are in jdk module and library specific packages are in java module.
Features of Java Module System: With the Modules component, following enhancements has been added
in Java 9:
 A new optional phase, link time is introduced. This phase is in-between compile time and run time.
During this phase, a set of modules can be assembled and optimized, making a custom runtime image
using jlink tool.
 javac, jlink, and java have additional options to specify module paths, which further locate definitions
of modules.
 JAR format updated as modular JAR, which contains module-info.class file in its root directory.
 JMOD format introduced, a packaging format (similar to JAR) which can include native code and
configuration files.
Declaring Module: In order to declare a module, we need to create a module-info.java in root folder of the
application. This file contains all the meta data or module descriptions. Example:
module-info.java
module com.hindustan.greetings {

}
Adding Dependent Modules: We can declare dependencies of other modules in the module using requires
keyword in the module. For example, if we want to use com.hindustan.util module then we can add the
declaration as follows:
module com.hindustan.greetings {
requires com.hindustan.util;
}
Adding Optional Modules: We can declare optional dependencies of other modules using static keyword in
the module. For example, if we want to use com.hindustan.logging module then we can add the declaration as
follows:
module com.hindustan.greetings {
requires com.hindustan.util;
requires static com.hindustan.logging;
}
Adding Transitive Modules: We can declare transitive dependencies of other modules using transitive
keyword in the module. For example, if we want to use com.hindustan.base module as dependency of
com.hindustan.util module then we can add the declaration as follows:
module com.hindustan.greetings {
requires com. hindustan.util;
requires static com. hindustan.logging;
requires transitive com. hindustan.base;
}
This means if a module wants to use com.hindustan.greetings then that module is required to add com.
hindustan.base module as well in module declaration.
Export Public Classes: By default, no public class of a package of a module is exposed to outside world. In
order to use the public class, we've to export it as shown below:
module com. hindustan.greetings {
requires com. hindustan.util;
requires static com. hindustan.logging;
requires transitive com. hindustan.base;
exports com. hindustan.greetings.HelloWorld;
}
Allow Reflection: By default, no private member of a package of a module is accessible via reflection. In
order to allow reflection to inspect the class or module, we've to use opens command as shown below:
module com. hindustan.greetings {
requires com. hindustan.util;
requires static com. hindustan.logging;
requires transitive com. hindustan.base;
exports com. hindustan.greetings.HelloWorld;
opens com. hindustan.greetings.HelloWorld;
}
If we need to allow complete module to be open for reflection, we can use open command as shown below:
open module com. hindustan.greetings {
requires com. hindustan.util;
requires static com. hindustan.logging;
requires transitive com. hindustan.base;
exports com. hindustan.greetings.HelloWorld;
}
Creating and Using Java Module: Following the steps to create a module say com.hindustan.greetings.
Step 1: Create a folder d:\>nkm_java\src. Now create a folder com.hindustan.greetings which is same as the
name of module we're creating.
Step 2: Create module-info.java in d:\>nkm_java\src\com.hindustan.greetings folder with following code.
//module-info.java
module com. hindustan.greetings { }
module-info.java is the file which is used to create module. In this step we've created a module named com.
hindustan.greetings. By convention this file should reside in the folder whose name is same as module name.
Step 3: Add the source code in the module. Create HelloWorld.java in d:\>nkm_java\src\com.
hindustan.greetings\com\hindustan\greetings folder with following code.
//HelloWorld.java
package com. hindustan.greetings;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
By convention, the source code of a module to lie in same directory which is the name of the module.
Step 4: Create a folder d:\>nkm_java\mods. Now create a folder com.hindustan.greetings which is same as
the name of module we've created. Now compile the module to mods directory.
d:/ >nkm_java > javac -d mods/com. hindustan.greetings
src/com. hindustan.greetings/module-info.java
src/com.hindustan.greetings/com/hindustan/greetings/HelloWorld.java
Step 5: Let's run the module to see the result. Run the following command.
d:/>nkm_java>java --module-path mods -m
com.hindustan.greetings/com. hindustan.greetings.HelloWorld
Here module-path provides the module location as mods and -m signifies the main module.
It will print the following output on console.
Hello World!
Java Default Methods: Prior to Java8, an interface could have only abstract methods. Default method
capability was added for backward compatibility so that old interfaces could be used to leverage the lambda
expression capability of Java 8. For example, List or Collection interfaces do not have 'forEach' method
declaration. Thus, adding such method will simply break the collection framework implementations. Java 8
introduces default method so that List/Collection interface can have a default implementation of forEach
method, and the class implementing these interfaces need not implement the same.
Syntax: The following is the syntax of the default method in interface in Java -
public interface InterfaceName
{
default return_type method_name(list of parameters)
{
//statements implementing method
}
}
Java Default Method Example:
interface Vehicle
{
// Default method must have an implementation
default void print()
{
System.out.println("I am a vehicle!");
}
}

// Implementing class needs not to implement the default method of an interface.


public class DefaultDemo implements Vehicle
{
public static void main(String args[])
{
DefaultDemo t = new DefaultDemo();
// Implementing class can access the default method as its own method
t.print();
}
}
Default Methods in Multiple Inheritance: With default functions in interfaces, there is a possibility that a
class is implementing two interfaces with same default methods. The following code explains how this
ambiguity can be resolved.
public interface Vehicle
{
default void print()
{
System.out.println("I am a vehicle!");
}
}
public interface FourWheeler
{
default void print()
{
System.out.println("I am a four wheeler!");
}
}
//First solution is to create an own method that overrides the default implementation.
public class Car implements Vehicle, FourWheeler
{
public void print()
{
System.out.println("I am a four wheeler car vehicle!");
}
}
Example: Overriding default method of interfaces with own implementation: In this example, we've
created two interfaces with same default method print(). As Car class is implementing both the interfaces, so
it has to override the default method otherwise compiler will complain for duplicate default methods. After
overriding the default method with own implementation, we can easily use the print method of the Car class
as shown below:
interface Vehicle
{
default void print()
{
System.out.println("I am a vehicle!");
}
}
interface FourWheeler
{
default void print()
{
System.out.println("I am a four wheeler!");
}
}
class Car implements Vehicle, FourWheeler
{
// overriding the default method will resolve the ambiguity
public void print()
{
System.out.println("I am a four wheeler car vehicle!");
}
}
public class DefaultMethodDemo
{
public static void main(String args[])
{
Car car = new Car();
car.print();
}
}
//Second solution is to call the default method of the specified interface using super.
public class car implements Vehicle, FourWheeler
{
public void print()
{
vehicle.super.print();
}
}
Example: Calling default method of interfaces: In this example, we've created two interfaces with same
default method print(). As Car class is implementing both the interfaces, so it has to override the default
method otherwise compiler will complain for duplicate default methods. After overriding the default method
with own implementation, we can easily use the print method of the Car class as shown below:
interface Vehicle
{
default void print()
{
System.out.println("I am a vehicle!");
}
}
interface FourWheeler
{
default void print()
{
System.out.println("I am a four wheeler!");
}
}
class Car implements Vehicle, FourWheeler
{
// use the default method of a interface
public void print()
{
FourWheeler.super.print();
}
}
public class DefaultMethodDemo
{
public static void main(String args[])
{
Car car = new Car();
car.print();
}
}
Static Default Methods in Java: An interface can also have static default methods from Java 8 onwards.
These static methods acts as helper or utility functions and helps in better encapsulation of code.
public interface Vehicle
{
default void print()
{
System.out.println("I am a vehicle!");
}
static void blowHorn()
{
System.out.println("Blowing horn!!!");
}
}
Example: Calling static default method of interface: In this example, we've created two interfaces with
same default method print(). As Car class is implementing both the interfaces, so it has to override the default
method. After overriding the default method with calls to interfaces implementation, we've called the static
method directly as shown below:
interface Vehicle
{
default void print()
{
System.out.println("I am a vehicle!");
}
static void blowHorn()
{
System.out.println("Blowing horn!!!");
}
}
interface FourWheeler
{
default void print()
{
System.out.println("I am a four wheeler!");
}
}
class Car implements Vehicle, FourWheeler
{
public void print()
{
// Call the Vehicle interface default print method
Vehicle.super.print();
FourWheeler.super.print();
// Call the Vehicle interface static blowHorn method
Vehicle.blowHorn();
System.out.println("I am a car!");
}
}
public class DefaultMethodDemo
{
public static void main(String args[])
{
Vehicle vehicle = new Car();
vehicle.print();
// Call the Vehicle interface static blowHorn method
Vehicle.blowHorn();
}
}
Private Interface Methods: Private and static private interface methods were introduced in Java 9. Being a
private method, such a method cannot be accessed via implementing class or sub-interface. This methods were
introduced to allow encapsulation where the implementation of certain method will be kept in interface only.
It helps to reduce the duplicity, increase maintainability and to write clean code.
interface Utility
{
public default int operate(int a, int b){ return sum(a, b); }
private int sum(int a, int b) {return a + b;}
}
public class DemoPrivate implements Utility
{
public static void main(String[] args)
{
DemoPrivate d = new DemoPrivate();
System.out.println(d.operate(2, 3));
}
}

Java Diamond Operator: The diamond operator was introduced in Java 7 to make code more readable for
Generics. A generic is a type of argument. Using generic we can pass any kind of object to be processed by
the class methods. For example, if we are creating a list of strings before Java 7, then we've to use the following
syntax to instantiate a list of strings with an ArrayList object. List<String> lst = new ArrayList<String>();
We can use diamond operator to simplify the above syntax: List<String> lst = new ArrayList<>();
But it could not be used with Anonymous inner classes. For example, we cannot omit the object type from
diamond operator in below syntax prior to Java 9.
Handler<Integer> intHandler = new Handler<Integer>(1){
@Override public void handle() { System.out.println(content); }};
Diamond Operator in Anonymous Class: In Java 9, the diamond operator can be used with an anonymous
class as well to simplify code and improve readability.
Handler<Integer> intHandler = new Handler<>(1)
{
@Override
public void handle()
{
System.out.println(content);
}
};
Diamond Operator in Java 7, Java 8: In below example, we've created anonymous classes for an abstract
class Handler accepting a generic argument and pass the object type while creating the anonymous class as
we have to pass the type argument otherwise program won't compile.
Example:
public class DiamondOperatorDemo
{
public static void main(String[] args)
{
//Create anonymous class to handle 1
Handler<Integer> intHandler = new Handler<Integer>(1) {
@Override public void handle() {System.out.println(content); }};
intHandler.handle();
// Create an Anonymous class to handle 2
Handler<? extends Number> intHandler1 = new Handler<Number>(2) {
@Override public void handle() { System.out.println(content); } };
intHandler1.handle();
Handler<?> handler = new Handler<Object>("test") {
@Override public void handle() { System.out.println(content); }};
handler.handle();
}
}
abstract class Handler<T>
{
public T content;
public Handler(T content) { this.content = content; }
abstract void handle();
}
Diamond Operator Java 9 Onwards: With Java 9, we can use <> operator with anonymous class as well.
Example: In below example, we've created anonymous classes for an abstract class Handler accepting a
generic argument but without the object type while creating the anonymous class as we need not to pass the
type argument. Compiler infers the type itself.
public class DiamondOperatorDemo
{
public static void main(String[] args)
{
// Create an Anonymous class to handle 1
Handler<Integer> intHandler = new Handler<>(1) {
@Override public void handle() { System.out.println(content); } };
intHandler.handle();
// Create an Anonymous class to handle 2
Handler<? extends Number> intHandler1 = new Handler<>(2) {
@Override public void handle() { System.out.println(content); }};
intHandler1.handle();
Handler<?> handler = new Handler<>("test") {
@Override public void handle() { System.out.println(content); } };
handler.handle();
}
}
abstract class Handler<T>
{
public T content;
public Handler(T content)
{
this.content = content;
}
abstract void handle();
}

Switch Expressions: 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. Java 12 introduces expressions to Switch statements and releases them as a preview
feature. Java 13 added a new yield construct to return a value from a switch statement. With Java 14, switch
expression now is a standard feature.
 Each case block can return a value using yield statement.
 In case of enum, default case can be skipped. In other cases, default case is required.
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
Where label1 to labeln are representing the various values to compare and we can use expression, a statement
to return a value or throw an expression. Once Java runtime matches any label in the left block to the arrow,
it moves to the right block of arrow and executes the expression (statement) and returns the result.
Example: Using case L -> Labels:
public class SwitchDemo
{
public static void main(String[] args)
{
//Old style of switch
System.out.println(getDayTypeOldStyle("Monday"));
System.out.println(getDayTypeOldStyle("Saturday"));
System.out.println(getDayTypeOldStyle(""));
//New style of switch
System.out.println(getDayType("Monday"));
System.out.println(getDayType("Saturday"));
System.out.println(getDayType(""));
}
public static String getDayType(String day)
{
// Evaluate switch expression and get a value
return switch (day) {case "Monday", "Tuesday", "Wednesday","Thursday", "Friday" ->
"Weekday";
case "Saturday", "Sunday" -> "Weekend"; default -> "Invalid day.";};
}
public static String getDayTypeOldStyle(String day)
{
String result = null;
// Evaluate relevant cases and get a value into result variable
switch (day)
{
case "Monday": case "Tuesday": case "Wednesday": case "Thursday": case "Friday":
result = "Weekday";
break;
case "Saturday": case "Sunday":
result = "Weekend";
break;
default:
result = "Invalid day.";
}
return result;
}
}
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. The yield statement returns the value and
finishes the switch execution.
Example: Using "case L:" Statements and the yield Statement:
Syntax: case label1, label2, ..., labeln -> expression; yield value;
case label1: case labeln: expression; yield value;
Where label1 to labeln are representing the various values to compare and we can use expression to perform
an operation and return value using yield statement. In this example, we've compared switch expressions using
yield statements. getDayType() method is using a switch expression with yield statement to get the result
whereas getDayTypeStyle1() method is using switch cases with colon(:) notation with yield statement to the
get the desired result.
public class SwitchDemo
{
public static void main(String[] args)
{
System.out.println(getDayTypeStyle2("Monday"));
System.out.println(getDayTypeStyle2("Saturday"));
// System.out.println(getDayTypeStyle2(""));
System.out.println(getDayType("Monday"));
System.out.println(getDayType("Saturday"));
System.out.println(getDayType(""));
}
public static String getDayType(String day)
{
return switch (day)
{
// We can use block statements to return a value using yield after executing other statements
case "Monday", "Tuesday", "Wednesday","Thursday", "Friday" ->
{
System.out.println("In Weekdays");
yield "Weekday";
}
case "Saturday", "Sunday" ->
{
System.out.println("In Weekends");
yield "Weekend";
}
default -> throw new IllegalStateException("Invalid day: " + day);
};
}
public static String getDayTypeStyle2(String day)
{
return switch (day){ case "Monday": case "Tuesday": case "Wednesday": case "Thursday":
case "Friday": yield "Weekday"; case "Saturday": case "Sunday": yield "Weekend";
default: throw new IllegalStateException("Invalid day: " + day); };
}
}

Text Blocks: Java made text blocks in Java 15 as a standard feature to handle multiline strings like
JSON/XML/HTML etc. It was introduced in Java 13 as a preview feature.
 Text Block allows to write multiline strings easily without using \r\n.
 Text Block string have same methods as String class methods like contains(), indexOf(), and length()
functions.
Purpose of introducing text block is mainly to declare multi-line strings most efficiently. Prior to text block,
we can declare multi-line strings using String concatenation, StringBuilder append method, String join method
but that approach is quite messy. As we have to use line terminators, delimiters etc to mark a new line. Text
block provides a better and alternate approach to define multiline string using a """, triple double-quotes mark.
Text Block Syntax: A text block is an enhancement to existing String object with special syntax where string
content should starts with """ with newline and ends with """. Any content within """ will be used as-is.
String textBlockJSON =
"""
{
"name" : "Akash",
"RollNO" : "101"
}
""";
Equivalent String can be written using older syntax as shown below:
String stringJSON = "{\r\n"
+ " \"Name\" : \"Akash\",\r\n"
+ " \"RollNO\" : \"101\"\r\n"
+ "}";
Example of Java Text Block:
public class TextBlockDemo
{
public static void main(String[] args)
{
String stringJSON = "{\r\n"
+ " \"Name\" : \"Akash\",\r\n"
+ " \"RollNO\" : \"101\"\r\n"
+ "}";
System.out.println(stringJSON);
String textBlockJSON = """
{
"name" : "Akash",
"RollNO" : "101"
}
""";
System.out.println(textBlockJSON);
}
}
Text Block String Operations: Text block is same as String and can be compared using equals() method or
equal operator. Text block supports all string operations like indexOf(), contains() etc.
// compare the content,
textBlockJSON.equals(stringJSON);
// compare the objects
textBlockJSON == stringJSON;
// check if text block contains a provided string or not
textBlockJSON.contains("Akash");
// get the length of string content
textBlockJSON.length()
Example:
public class TextBlockDemo
{
public static void main(String[] args)
{
String stringJSON = "Akash";
String textBlockJSON = """
Akash """;
// compare the content
System.out.println(textBlockJSON.equals(stringJSON));
// compare the objects
System.out.println(textBlockJSON == stringJSON);
// text block supports all string operations
System.out.println("Contains: " + textBlockJSON.contains("Akash "));
System.out.println("indexOf: " + textBlockJSON.indexOf("Akash "));
System.out.println("Length: " + textBlockJSON.length());
}
}
Text Block Methods:
 stripIndent() - removes incidental white spaces from the start and end of the string.
 translateEscapes() - translate the escape sequences as per the string syntax.
 formatted() - similar to String format() method to support formatting in text block strings.
Example: Consider the following example −
public class TextBlockDemo
{
public static void main(String[] args)
{
String textBlockJSON = """
{
"Name" : "%s",
"Roll No" : "%s"
}
""".formatted("Akash", "101");
System.out.println(textBlockJSON);
}
}

Java – Record: The record feature helps in creating immutable data objects. In the Java 15 version, record
types were enhanced further. In Java 14 and 15, in order to use a record, a flag --enable-preview has to be
passed. From Java 16 onwards, this flag is not required as the record is a standard part of JDK.
Purpose of a Java Record: The prime purpose of a record is to create a data object or a POJO which is used
to carry data in application program flow. In a multi-tier program, Domain/Model objects store the data
captured from the data source and then these model objects are passed further to the application/UI layer to
process the data and vice versa where UI/Application stores data in data objects and then pass these objects to
Data layer to populate data sources.
As these data objects contain a lot of fields, developers are required to write a lot of setter/getter methods,
parameterized constructors, overridden equals methods, and hashcode methods. In such a scenario, the record
comes to the rescue as it provides most of the boilerplate code and the developer can focus on required
functionalities only.
Features of Java Record: Following are the features of the record which makes the record an exciting feature:
 Record objects have an implicit constructor with all the parameters as field variables.
 Record objects have implicit field-getter methods for each field variable.
 Record objects have implicit field setter methods for each field variable.
 Record objects have an implicit sensible implementation of hashCode(), equals(), and toString()
methods.
 With Java 15, native methods cannot be declared in records.
 With Java 15, implicit fields of record are not final and modification using reflection will throw
IllegalAccessException.
Example Without Using Java Record: Let's create a simple program without using record where we're
creating a Student object and printing its details. The Student has three properties, id, name and className.
In order to create a student, we've created a parameterized constructor, setter, getter methods, equals and
hashcode methods.
public class RecordDemo
{
public static void main(String args[])
{
// create student objects
Student student1 = new Student(1, "Anita", "B.Tech");
Student student2 = new Student(2, "Akanksha", "B.Tech");
// print the students
System.out.println(student1);
System.out.println(student2);
// check if students are same
boolean result = student1.equals(student2);
System.out.println(result);
// check if students are same
result = student1.equals(student1);
System.out.println(result);
// get the hashcode
System.out.println(student1.hashCode());
System.out.println(student2.hashCode());
}
}
class Student
{
private int id;
private String name;
private String className;
Student(int id, String name, String className)
{
this.id = id;
this.name = name;
this.className = className;
}
public int getId() {return id;}
public void setId(int id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public String getClassName() {return className;}
public void setClassName(String className) {this.className = className;}
@Override
public String toString() {return "Student[id: " + id + ", name: " + name + ", class: " + className + "]";}
@Override
public boolean equals(Object obj)
{
if(obj == null || !(obj instanceof Student) ) {return false;}
Student s = (Student)obj;
return this.name.equals(s.name) && this.id == s.id && this.className.equals(s.className);
}
@Override
public int hashCode()
{
int prime = 19;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((className == null) ? 0 : className.hashCode());
result = prime * result + id;
return result;
}
}
Example Using Java Record:
public class RecordDemo
{
public static void main(String args[])
{
// create student objects
Student student1 = new Student(1, "Anita", "B.Tech");
Student student2 = new Student(2, "Akanksha", "B.Tech");
// print the students
System.out.println(student1);
System.out.println(student2);
// check if students are same
boolean result = student1.equals(student2);
System.out.println(result);
// check if students are same
result = student1.equals(student1);
System.out.println(result);
// get the hashcode
System.out.println(student1.hashCode());
System.out.println(student2.hashCode());
}
}
record Student(int id, String name, String className) {}
We can add custom methods in records as well. But generally it is not required.
Java Record for Sealed Interfaces: As records are final by default and can extend interfaces. We can define
sealed interfaces and let records implement them for better code management.
Example: Use of Java Record for Sealed Interfaces
public class SealedRecordDemo
{
public static void main(String[] args)
{
Person employee = new Employee(44, "Rahul");
System.out.println(employee.id());
System.out.println(employee.name());
}
}
sealed interface Person permits Employee, Manager
{
int id();
String name();
}
record Employee(int id, String name) implements Person {}
record Manager(int id, String name) implements Person {}
Overriding Methods of Java Records: We can override a record method implementation easily and provide
our own implementation.
Example: Override Java Record Methods:
public class RecordOverride{
public static void main(String args[]) {
// create student objects
Student student = new Student(1, "Ankur", "B.Tech");
System.out.println(student);
}
}
record Student(int id, String name, String className)
{
public String toString(){return "Id: " + id + ", Name: " + name + ", class: " + className ;}
}

Sealed Classes and Interfaces: A sealed class/interface feature is added to Java to provide developers a fine
fine-grained control over inheritance. A sealed class can define the subtypes that are permitted to extend it
while other classes cannot extend it. The following are salient points to consider for a sealed class:
 A sealed class is declared using a sealed keyword.
 Sealed classes allow to declaration of which class can be a subtype using the permits keyword.
 A class extending sealed class must be declared as either sealed, non-sealed, or final.
 Sealed classes help in creating a finite and determinable hierarchy of classes in inheritance.
Sealed Interface: An interface can be marked as sealed interface using sealed keyword and then using permits
keywords, we can add the interfaces which can extend this interface. Example:
public sealed interface Person permits Employee, Manager {}
Sealed Interface Example: In this example, we've created a sealed interface Person which permits Employee
and Manager interfaces to extend it. Employee and Manager interfaces have different methods to get the ID
of a person. Now in order to get ID of a person, we're using instanceof operator to check an instance being of
Employee or Manager and get the corresponding ID. Thus we can see, that knowing the permissible interfaces
upfront helps in development for such scenarios.
public class SealedDemo
{
public static void main(String[] args)
{
// create an instance of Manager
Person manager = new CorpManager(43, "Vikash");
// get the id
System.out.println("Id: " + getId(manager));
}
public static int getId(Person person)
{
// check if person is employee then return employee id
if (person instanceof Employee){return ((Employee) person).getEmployeeId();}
// if person is manager then return manager id
else if (person instanceof Manager) {return ((Manager) person).getManagerId();}
return -1;
}
}
// A sealed interface Person which is to be inherited by Employee and Manager interfaces
sealed interface Person permits Employee, Manager
{
String getName();
}
// Employee and Manager interfaces have to extend Person and can be sealed or non-sealed
non-sealed interface Employee extends Person
{
int getEmployeeId();
}
non-sealed interface Manager extends Person
{
int getManagerId();
}
class CorpEmployee implements Employee
{
String name;
int id;
public CorpEmployee(int id,String name)
{
this.name = name;
this.id = id;
}
public String getName() {return name;}
public int getEmployeeId() {return id;}
}
class CorpManager implements Manager
{
String name;
int id;
public CorpManager(int id,String name)
{
this.name = name;
this.id = id;
}
public String getName() {return name;}
public int getManagerId() {return id;}
}
Sealed Class: Similar to sealed interface, a class can be marked as sealed class as well using sealed keyword
and then using permits keywords, we can add the subclasses which can extend this class. A subclass needs to
have a modifier as sealed/final or non-sealed. A subclass with non-sealed is open for all classes to extend.
public abstract sealed class Person permits Employee, Manager { }
public final class Manager extends Person {}
public non-sealed class Employee extends Person {}
Constraints: There are few constraint on usage of sealed classes which we should notice while extending a
sealed class.
 Permitted subclass should be part of same module as of sealed class.
 Permitted subclass has to extend the sealed class.
 A permitted subclass has to use any one of final, sealed, or non-sealed modifier.
Sealed Class Example:
public class SealedDemo
{
public static void main(String[] args)
{
// create an instance of Manager
Person manager = new Manager(43, "Vikash");
// get the id
System.out.println("Id: " + getId(manager));
}
public static int getId(Person person)
{
// check if person is employee then return employee id
if (person instanceof Employee) {return ((Employee) person).getEmployeeId();}
// if person is manager then return manager id
else if (person instanceof Manager) {return ((Manager) person).getManagerId();}
return -1;
}
}
// a sealed class Person which is to be inherited by Employee and Manager classes
abstract sealed class Person permits Employee, Manager
{
String name;
String getName() {return name;}
}
// Employee class has to extend Person and should have a modifier
final class Employee extends Person
{
String name;
int id;
Employee(int id, String name)
{
this.id = id;
this.name = name;
}
int getEmployeeId() {return id;}
}
// We can mark a sub-class as non-sealed, so that it can be extended if required
non-sealed class Manager extends Person
{
int id;
Manager(int id, String name)
{
this.id = id;
this.name = name;
}
int getManagerId() {return id;}
}

You might also like