Unit 3 1
Unit 3 1
Functional Interfaces introduced in Java 8 allow us to use a lambda expression to initiate the
interface's method and avoid using lengthy codes for the anonymous class implementation.
Various built-in interfaces were declared with @FunctionalInterface annotation and made
functional from Java 8.
No function is independently present on its own in java. They are part of classes or interfaces.
And to use them we require either the class or the object of the respective class to call that
function.
The function can do anything a variable is capable to perform like passing a function as a
parameter, a function returned by another function, etc.
Functional interfaces were introduced in Java 8. A functional interface can contain only one
abstract method and it can contain any number of static and default (non-abstract) methods.
Abstract methods are methods that do not require implementation during their declaration and
must be overridden by the class implementing the interface.
Default methods can be directly used in a class implementing the interface as well as can be
overridden and redefined. Static methods are required to be called using the name of the
interface preceding the method name. These cannot be overridden by the classes implementing
the interface.
Functional Interface in Java is also called Single Abstract Method (SAM) interface. From
Java 8 onwards, to represent the instance of functional interfaces, lambda expressions are used.
Lambda expressions are anonymous (don't have names) shortcode blocks that can take
parameters and return a value just like methods.
1
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
@FunctionalInterface
// Abstract method
void myMethod();
// method implementation
// method implementation
// public class
// main method
2
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
// perform operations
};
The functional interface in Java is defined just like normal interfaces. It should only have one
abstract method. Though it can contain multiple default or static methods.
Abstract keyword: Though from Java 8, interfaces can have static and default methods. By
default, methods in interfaces are abstract only. So it is not mandatory to mention
the abstract keyword before the method.
If we try to add more than one abstract method, the compiler flags an Unexpected
@FunctionalInterface annotation message.
// interface
@FunctionalInterface
3
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
interface Sample{
// abstract method
// public class
4
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
There are many interfaces that are converted into functional interfaces. All these interfaces are
annotated with @FunctionalInterface. These interfaces are as follows –
In Java four main kinds of functional interfaces which can be applied in multiple situations
as mentioned below:
1. Consumer
2. Predicate
3. Function
4. Supplier
Lambda Expression
Lambda expression is a new and important feature of Java which was included in Java SE 8. It
provides a clear and concise way to represent one method interface using an expression. It is
very useful in collection library. It helps to iterate, filter and extract data from collection.
5
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
The Lambda expression is used to provide the implementation of an interface which has
functional interface. It saves a lot of code. In case of lambda expression, we don't need to define
the method again for providing the implementation. Here, we just write the implementation
code.
Java lambda expression is treated as a function, so compiler does not create .class file.
1. Lambda expressions are used with functional interfaces. They cannot be used with
interfaces that have more than one abstract method.
3. We don’t have to create classes that implement a functional interface to create objects.
We can use lambda expressions instead.
Note1 -> Lambda expression is a way of implementing the only abstract method in a functional
interface without having a class that implements the functional interface.
Note2 -> Lambda expressions allows us to use interfaces without having their implementing
classes.
6
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
No Parameter Syntax
() ->
Example
@FunctionalInterface //It is optional
interface Drawable
{
public void draw();
}
//with lambda
7
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
Drawable d2=()->
{
System.out.println("Drawing "+width);
};
d2.draw();
}
}
import java.util.*;
@FunctionalI+nterface
interface PersonalGreet
8
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
System.out.println(hello.greeting(name));
(p1,p2) ->
{
//Body of multiple parameter lambda
}
@FunctionalInterface
public interface Calculation
{
9
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
}
}
return statement in lambda expressions:
In lambda expressions, if there is just one statement, then there is no need for a return statement.
They only need to use curly braces{} for using return statement with single statement in
lambda expression.
//Lambda without return keyword in case of single statement (a,b) -> (a+b);
//Lambda with return keyword in case of single statement. Here curly braces are must
(a,b) -> { return (a+b); };
But when there are multiple statements, it is mandatory to use return statements.
Here, we just have to import the interface. We are not required to implement it in our class to
use the Functional Interface in Java.
Lambda expressions are just like methods in java but they make the code short, clean, and
readable. They are also called anonymous functions. Thus, they are used more frequently with
functional interfaces in Java.
Syntax:
10
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
};
tempName.absName();
InterfaceName is the functional interface we are going to use in our main method. We neither
have to implement an interface nor have to override the abstract method.
We can directly give implementation to the abstract method using a lambda expression. To call
this temporarily implemented method we have to use the syntax tempName.AbstractMethod().
import java.util.*;
@FunctionalInterface
interface PersonalGreet
11
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
System.out.println(hello.greeting(name));
Explanation: In the example above, we are not required to override any method. We can give
implementation to the abstract method of the functional interface in the main method itself
using a lambda expression. It takes one String variable as an argument and returns another
String.
Another way is to implement the interface in our class. By doing so we will have to override
the abstract method. Then to call the method we are supposed to create the object of our
class and use the following syntax obj.AbstarctMethod().
Syntax:
@FunctionalInterface
interface Sample
{
void absMethod();
}
public class className implements Sample
{
@Override
public static void absMethod(){
// implementation
}
12
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
Explanation: In the example above Sample is an interface. Our class Main implements the
interface, thus it has to override the abstract method of the interface. To call this abstract method
in our main method we have to create the object of our Main class.
import java.util.*;
@FunctionalInterface
interface PersonalGreet
System.out.println(obj.greeting(name));
13
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
// interface implementation
@FunctionalInterface
interface StaticandDefaultMethods
// abstract method
// default methods
return a+b;
return a-b;
14
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
// static methods
return a*b;
return a/b;
// public class
int a = 8;
int b = 6;
15
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
// abstract method
return x*x;
Java has pre-defined or built-in functional interfaces for commonly occurring cases.
• Runnable - It contains only the run() method. It is used to write applications that can
run on a separate thread.
16
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
• Comparable - This interface contains only the compareTo() method. The objects of the
class that implements the Comparable interface can be compared the objects of the class
that implements Comparable interface can be compared and sorted.
• Callable - It only contains the call() method. This method is used to monitor the
progress of a function being executed by a thread.
Method References
Method references are a new feature of Java 8. They are a short and easily readable writing
syntax for a lambda expression.
We use a method reference to refer to a functional interface method. We can replace a method
reference for our lambda expression whenever we just refer to a method with a lambda
expression.
A functional interface has only one abstract or unimplemented method other than
different default and static methods. It exhibits only a single functionality.
Method references refer to the method of functional interface. It is an easy way of writing
lambda expressions used to call a method.
It is represented using double colon (::) operator. Lambda expressions can be replaced with
method references.
System.out::println
17
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
In method reference, the :: operator divides the method name from the class or object name.
syntax:
<class-name> :: <static-method-name>
@FunctionalInterface
public interface Executable
{
void execute();
}
public class MethodReferenceExample
{
//we will use this static method to refer execute() method of Executable interface
18
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
}
}
Syntax :
<instance-variable-name> :: <instance-method-name>
@FunctionalInterface
public interface Executable {
void execute();
}
public class MethodReferenceExample {
}
}
3. Reference to a constructor
A functional interface’s abstract method can also be referred by a constructor of a class.
Syntax :
19
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
<class-name> :: new
//no-arg constructor
public MethodReferenceExample()
{
System.out.println("MethodReferenceExample object initialized.");
}
}
}
Stream API
Stream API is a newly added feature to the Collections API in Java 8. A stream represents a
sequence of elements and supports different operations (Filter, Sort, Map, and Collect) from a
collection. We can combine these operations to form a pipeline to query the data.
20
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
As we can see, in the first step a stream is created with the given input. This input can be any
type of data ranging from arrays, collections also primitives. Then this stream is passed on to
the intermediate operations. These in turn produce the output streams and give them to the
terminal operations. As a result of which output is generated form the terminal operations.
As we can see stream has 3 step
These methods take collections and arrays as input to produce streams out of them.
1.1 Creating Empty Stream
The empty() method should be used in case of the creation of an empty stream:
Stream<String> stream = Stream.empty();
21
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
stream.forEach(System.out::println);
1.2 Create java stream from collections
While creating streams from collection object we can use .stream() method on the
collection object like lists, sets etc.
//Creation of simple stream from list
List<String> names = new Arraylist():
names.add(“Alpha”);
names.add(“Beta”);
Stream stream = names.stream();
//Creation of simple stream from Set
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
Stream stream = set.stream();
22
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
streamIterated.forEach(System.out::println);
1.7 Stream of File
Java NIO class Files allows to generate a Stream of a text file through the lines() method.
Every line of the text becomes an element of the stream:
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
23
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
import java.util.Random;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
The operations which returns source object (stream) after processing are known as intermediate
operations.(Also known as Non-Terminal operations).
As they return stream object we can again call another intermediate or terminal operation on
them. This helps to create chained operations one after the other consequently [Chain of
Responsibility Design Pattern in Java] , to perform multiple operations on same element. In
chained operation the output of one operation becomes the input of next.
Most important point to note stream operations does not change source objects, it returns new
stream with updated elements.
The most common operations on java 8 steam object
24
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
The filter() method accepts a Predicate to filter all elements of the stream. This operation
is intermediate, enabling us to call another stream operation (e.g. forEach()) on the result.
25
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
3.1 Stream.collect() : The collect() method collects the result of the intermediate operations
performed on the stream and returns it.
List<Integer> numbers = new ArrayList<>(){4, 7, 1, 2};
Set square = numbers.stream().map( n -> n*n ).collect(Collectors.toSet());
3.2 Stream.forEach() : The forEach() method is used to iterate through every element of the
stream.
List<Integer> numbers = new ArrayList<>(){4, 7, 1, 2};
numbers.stream().map(x->x*x).forEach(y->System.out.println(y));
3.3 Stream.match()
Various matching operations can be used to check whether a given predicate matches the
stream elements. All of these matching operations are terminal and return a boolean result.
boolean matchedResult = memberNames.stream()
.anyMatch((s) -> s.startsWith("A"));
System.out.println(matchedResult); //true
matchedResult = memberNames.stream()
.allMatch((s) -> s.startsWith("A"));
System.out.println(matchedResult); //false
matchedResult = memberNames.stream()
.noneMatch((s) -> s.startsWith("A"));
System.out.println(matchedResult); //false
3.4 Stream.count()
The count() is a terminal operation returning the number of elements in the stream as
a long value.
long totalMatched = memberNames.stream()
.filter((s) -> s.startsWith("A"))
.count();
System.out.println(totalMatched); //2
3.5 Stream.reduce()
26
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
The reduce() method performs a reduction on the elements of the stream with the given
function. The result is an Optional holding the reduced value.
In the given example, we are reducing all the strings by concatenating them using a
separator #.
Optional<String> reduced = memberNames.stream()
.reduce((s1,s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
The simplest way to think of Java streams is to imagine a list of objects disconnected from each
other, entering a pipeline one at a time. You can control how many of these objects are entering
the pipeline, what you do to them inside it, and how you catch them as they exit the pipeline.
We can obtain a stream from a collection using the .stream() method.
For example, we have a dropdown of languages in the header section and want to print the
dropdown list using both for loop & Stream API.
Let’s explain this with an array List example:
List<String>languages = new ArrayList <String>();
languages.add(“English”);
languages.add(“German”);
languages.add(“French”);
If we need to print the list of members,
Using For loop:
For(String language:languages)
{
System.out.println(language);
}
Usage of Stream API Methods:
Using stream API, we can obtain the stream by calling the .stream() method on the languages
array list and printing it in the following way:
Languages.stream().forEach(System.out::println)
After getting the stream, we called the forEach() method & pass the action we wanted to take
on each Element, then printed the member value on the console using the System.out.println
method.
27
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
After getting a stream from a collection, we can use that stream to process the collection’s
elements.
stream.Filter():
The filter() method is used to filter a stream. Let’s use the above stream example to filter out
the languages list based on specific criteria, i.e., items starting with ‘E.’
stream.filter(item->item.startsWith(“E”));
The filter() method takes a predicate as a parameter. The predicate interface contains a function
called boolean test(T t) that takes a single parameter and returns a Boolean. In this example,
we passed lambda expression (item->item.startsWith(“E”)) to the test() function.
When the filter() method is called on a stream, the filter passed as a parameter to the filter()
function is stored internally.
stream.sort():
We can sort a stream by calling the sort() method. Let’s use the sort() method on the above
languages list, as shown in the following code:
languages.stream().sorted().forEach(System.out::println);
The above method would help sort the elements in alphabetical order.
stream.map():
Stream provides a map() method to map the elements of a stream into another new object. Let’s
use the previous example and convert the elements of the languages list to uppercase, as shown
below:
language.stream().map(item->item.uppercase().forEach(System.out::println));
This will map all the elements that are strings in the language collection to their uppercase
equivalents.
stream.collect():
Stream API provides a collect() method for processing on the stream interface. When the
collect() method is invoked, filtering and mapping will occur, and the object obtained from
those actions will be collected. Let’s take the previous example and obtain a new list of
languages in uppercase, as shown in the below example:
List<String>
case language =
languages.stream().map(item>item.toUpperCase()).collect(collectors.toList()));
System.out.println(ucaselanguage);
First, it creates a stream, adds a map to convert the strings to uppercase, and collects all objects
in a new list.
28
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
1. Filter: It is kind of conical operation, i.e. for ‘n’ inputs you would receive ‘0...n’ outputs.
2. Map: It is kind of cylinder operation, i.e. for ‘n’ input you would receive ‘transform (n)’
outputs.
3. Reduce: Operation which reduces the stream back to collection or some primitive values.
29
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
Base64 encoding is a technique to convert binary data into an ASCII string format using a set
of 64 characters. It is commonly used in data transmission (like email and URL encoding),
storing binary data (like images or files) in text-based formats (like JSON or XML), and in
cryptographic applications.
Base64 decoding is the reverse process of encoding: it converts a Base64-encoded string back
into its original binary or text form.
The Base64 utility class is part of java.util class and provides static methods for Base64
encoding and decoding scheme.
• Basic − Output is mapped to a set of characters lying in A-Za-z0-9+/. The encoder does
not add any line feed in output, and the decoder rejects any character other than A-Za-
z0-9+/.
• URL − Output is mapped to set of characters lying in A-Za-z0-9+_. Output is URL and
filename safe.
30
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
The Basic encoder uses the Base64 Alphabet for encoding and decoding. It will not add any
line separators to the encoded string. This encoder uses characters lying in A-Za-z0-9+/
character set.
Step-by-step:
2. Group: Every 3 bytes (24 bits) of input are split into four 6-bit groups.
+ => 62
/ => 63
4. Padding: If input is not a multiple of 3 bytes, it's padded with = to make the output a
multiple of 4 characters.
import java.util.Base64;
31
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
byte[] b = str.getBytes();
System.out.print(encodedStr);
This encoding scheme added padding character(=) if the encoded string's length is not a
multiple of three.
import java.util.Base64;
byte[] b = str.getBytes();
32
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
System.out.print(encodedStr);
→If we don't want this padding, we can use the withoutPadding() method on the encoder.
import java.util.Base64;
public class Base641
{
public static void main(String args[])
{
String str = "java";
byte[] b = str.getBytes();
String encodedStr=Base64.getEncoder().withoutPadding().encodeToString(b);
System.out.print(encodedStr);
}
}
33
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
Base64 Decoding
It will take an encoded string as input and returns the decoded string.
import java.util.Base64;
byte[] b = str.getBytes();
34
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
Base64 URL Encoding and Decoding by using the URL and Filename safe Base64
Alphabet
Base64 class handles URL encoding and decoding by using the URL and Filename safe
Base64 Alphabet. This encoder uses characters lying in A-Za-z0-9+/ character set. It encodes
using the URL and Filename safe type base64 encoding scheme.
We can use the getUrlEncoder() method to obtain a Base64 URL encoder. Then, we can use
the encodeToString() method.
Similarly, we have a getUrlDecoder() method that returns a URL decoder. Again, we can use
the decode() method with this decoder.
import java.util.Base64;
byte[] b= urlToEncode.getBytes();
//Encoding
//Decoding
35
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
MIME stands for Multipurpose Internet Mail Extension, and the Base64 class uses the
Base64 Alphabet for its encoding and decoding operations. MIME Base64 encoder encodes the
provided string content to MIME friendly format.
Each line ends with a carriage return(\r) followed by a linefeed(\n) as the line separator.
Note that no line separator is present at the end of the encoded string.
We can use the getMimeEncoder() and the encodeToString() methods for the encoding.
import java.util.Base64;
import java.util.UUID;
36
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
sb.append(UUID.randomUUID().toString());
//Encoding
→To decode, we will use the getMimeDecoder() to obtain a decoder, and then we will use
the decode() method.
import java.util.Base64;
import java.util.UUID;
37
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
sb.append(UUID.randomUUID().toString());
//Encoding
//Decoding
38
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
The forEach() method in Java is a utility function to iterate over a Collection (list, set or map)
or Stream. This method was introduced in Java 8 as part of the Java Stream API, which provides
a functional programming approach to processing sequences of elements.
→The forEach() performs a given Consumer action on each item in the Collection.
→It is commonly used with lambda expressions to provide a clear and concise way to loop
through elements.
→It accepts a single parameter of type Consumer, which is a functional interface. This makes
it ideal for lambda expressions and method references.
→It provides a more concise and readable way to iterate over collections compared to
traditional for-loops.
collection.forEach(action);
Where collection is any object that implements the Iterable interface, and action is an instance
of a class that implements the Consumer interface.
Takes Consumer function as input which contains the logic we need to implement. We can
implement the consumer function using lambda function or method reference.
list.forEach(System.out::println);
Since Java 8, the forEach() has been added in the following classes or interfaces:
• Map interface – This makes forEach() operation available to all map classes.
39
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
Internally, the forEach() uses the enhanced for-loop for iterating through the collection items.
So using the enhanced for-loop will give the same performance as forEach() method.
import java.util.Arrays;
import java.util.List;
names.forEach(System.out::println);
The forEach method can also be used with a Map. Since a Map doesn't implement Iterable, we
need to use the forEach method on the entry set or key set.
import java.util.HashMap;
import java.util.Map;
40
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
ages.put("Alice", 30);
ages.put("Bob", 25);
ages.put("Charlie", 35);
ages.forEach((name, age) -> System.out.println(name + " is " + age + " years old"));
ages.entrySet().forEach(System.out::println);
import java.util.Arrays;
import java.util.List;
41
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
names.stream().forEach(...) creates a stream from the list and then iterates over it, performing
the specified action.
Java try-with-resources
A resource is an object that must be closed after the program is finished with it.
→When we connect to database, we must explicitly close database connection before JVM
shutdown.
→If we open a file we must explicitly close file before JVM shutdown and so on.
In order close these resources we generally use finally block (till java 6) since finally block
always executes irrespective of exceptions and errors.
The try with resources statement is a try statement that declares one or more resources.
The try with resources statement ensures that each resource is closed at the end of the try block
execution.
42
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
// catch block
Example 1: try-with-resources
import java.io.*;
class Main
String line;
System.out.println("Line =>"+line);
catch (IOException e)
43
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
Before Java 7 introduced this feature, we had to use the finally block to ensure that the
resource is closed to avoid resource leaks.
import java.io.*;
class Main {
BufferedReader br = null;
String line;
try {
44
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
System.out.println("Line =>"+line);
catch (IOException e)
finally
try
if (br != null)
br.close();
catch (IOException e)
45
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
We can declare more than one resource in the try-with-resources statement by separating them
with a semicolon;
import java.io. *;
import java.util.*;
class Main {
while (scanner.hasNext()) {
writer.print(scanner.nextLine());
When multiple declarations are made, the try-with-resources statement closes these resources
in reverse order. In this example, the PrintWriter object is closed first and then
the Scanner object is closed.
46
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
// code
If we declared the resource outside the block in Java 7, it would have generated an error
message.
try (scanner)
// code
To deal with this error, Java 9 improved the try-with-resources statement so that the reference
of the resource can be used even if it is not declared locally. The above code will now execute
without any compilation error.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
47
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
try
try (reader) {
String line;
System.out.println(line);
catch (IOException e) {
In this example:
• The reader is then used directly in the try-with-resources statement without needing to
be re-declared.
• The resource reader will still be automatically closed when the try block is exited.
48
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
2. Readability: Makes the code easier to read and understand by keeping the resource
initialization separate from the resource management.
3. Flexibility: Allows more flexible resource management, especially when resources are
initialized in different parts of the code before being used in the try-with-resources
statement.
Annotations
Java annotations are metadata (data about data) for our program source code.
They provide additional information about the program to the compiler but are not part of the
program itself. These annotations do not affect the execution of the compiled program.
Java annotations are defined using the @ symbol before the annotation name.
1. Marker Annotations:
49
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
These are the simplest type of annotation and do not contain any members or data. They
simply mark a declaration, providing information about its nature. Example: @Override.
2. Single-Value Annotations:
These annotations have one member. When applying them, the member's name can be
omitted if it is named "value." Example: @SuppressWarnings("unchecked").
3. Full Annotations:
These annotations have multiple members, each with a name and a
value. Example: @Retention(RetentionPolicy.RUNTIME).
4. Type Annotations:
Introduced in Java 8, these annotations can be applied to type usages, such as method return
types, field types, and type parameters. Example: @NotNull String message.
5. Repeatable Annotations:
Introduced in Java 8, these annotations can be applied multiple times to the same
declaration. They require the use of the @Repeatable meta-annotation. Example:
@interface Roles
{
Role[] value();
}
@Repeatable(Roles.class)
@interface Role {
String value();
}
@Role("Admin")
@Role("User")
class Security {
}
6. Predefined / Built-in Annotations:
50
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
class A
class B extends A {
@Override
51
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
class Main {
B b = new B();
b.displayInfo();
7. Custom Annotations
We can also define our own annotations. using the @interface keyword. These annotations can
be used for various purposes, such as configuration, code generation, and aspect-oriented
programming.
Type Annotations
Before Java 8, annotations could be applied to declarations only. Now, type annotations can be
used as well. This means that we can place annotations wherever we use a type.
Previously, annotations could only be used on declarations (classes, methods, fields, etc.). With
type annotations, we can annotate almost any use of a type.
Type Defnitions:
This declaration specifies non-null variable str of type String to avoid NullPointerException.
52
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
Type casts:
Method returns:
return name;
Method parameters:
this.name = name;
Java 8 also introduced repeating annotations, which allow us to apply the same annotation
multiple times to a single element. To define a repeating annotation, we need to:
Repeating Annotations: Allow the same annotation to be applied multiple times to the same
element.
3. Use the @Repeatable meta-annotation to associate the repeatable annotation with its
container.
53
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Schedule {
String dayOfWeek();
String time();
}
Step 2: Define the Container Annotation
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Schedules {
Schedule[] value();
}
Step 3: Use the @Repeatable Meta-Annotation
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Schedules.class)
public @interface Schedule {
String dayOfWeek();
String time();
}
Using Repeating Annotations
Now we can apply the @Schedule annotation multiple times to the same element:
@Schedule(dayOfWeek = "Monday", time = "09:00")
@Schedule(dayOfWeek = "Wednesday", time = "09:00")
54
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
Java Modules
Java Platform Module System is a new feature added into Java 9 version. It allows us to collect
related packages and files into a single unit called a module.
Before Java 9, there was no concept of the module system that leads to increase the application
size and difficult to move around. But in Java 9, the whole JDK or Java APIs have
been restructured into a set of modules so that we can use only the required module for our
project. Java Module System is a higher level of aggregation above Java packages.
Java modules is a way of aggregating or categorizing the Java APIs available in the Java core
platform, so that when we are developing a new Java application we can only include the
required Java libraries in our project, rather than having the complete Java platform APIs.
Modules that are related to Java Core such as the Java SE and JDK are known as system
modules,
Modules that are designed to use modules and defined in the compiled module-info.class are
called application modules.
Since JDK 9 is built upon modular approach, we can check the built-in module by using the --
list-modules command into terminal or cmd.
java --list-modules
According to Java Specification, the key goals of modularizing the Java SE platform are
• Reliable configuration
55
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
• Strong encapsulation
• Improved performance
Apart from JDK, Java also allows us to create our own modules so that we can develop a
module-based application.
To use a module, including the jar file into module path instead of the classpath. A modular jar
file added to classpath is a normal jar file and the module-info.class file will be ignored.
For example, let's create a folder java9 that contains java.modules and it itself contains
a module-info.java (module-descriptive) file and a Java source file as Hello.java.
java9/
-java.modules
-Hello.java
-module-info.java
56
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
// Hello.java
package com.aktu;
System.out.println("Hello, Modules!");
module java.modules
requires java.base;
• Module’s name
• Module’s dependencies
• Module's visibility: specify the packages it explicitly makes available to other modules.
The module name in the module-info file should be the same as the module name
directory(java.modules).
57
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
--module-source-path is a flag that represents the module location. Since our module is
located in the java9 folder, we used java9 in the command.
-d represents the directory to store our compiled module files. module-src is a directory in
which .class files will be stored.
After successfully compiling the code. It will be stored into a separate directory and if we see
its directory structure then it looks like:
java9/
-java.modules
-Hello.java
-module-info.java
-module-src
-java.modules
-com
-aktu
-Hello.class
-module-info.class
After successfully compile the Java code, now let's run the code and check whether our code
produces the desired result. Use the below command.
58
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
Hello, Modules
An anonymous inner class in Java is a special type of inner class that doesn't have a name. It's
used to create a single object of a class that extends a superclass or implements an interface, all
within a single statement. This is useful when we need a simple, one-time-use implementation
without defining a separate named class.
An Anonymous Inner Class in Java is a type of inner class without a name, declared and
instantiated all in one expression. It's typically used to create one-time-use subclasses or
implementations of interfaces, especially when the implementation is short and used only
once.
@Override
};
task.run();
Here:
• Runnable is an interface.
• The new Runnable() { ... } defines an anonymous inner class implementing the
Runnable interface.
59
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
For example: List<String> denotes list of strings, Set<Integer> denotes set of integers etc…
Empty diamond operator <> is introduced from Java 7 to implement automatic type inference
feature in the code.
Empty diamond operator removes the redundant code by leaving the generic type in the right
side of the declaration statement.
Till Java 9, empty diamond operator is allowed to use with normal classes only. It is not
allowed to use with anonymous inner classes.
From java 9 we can use diamond operator for anonymous inner classes with optional type
parameter. Diamond operator can be empty or can be with type parameter. If type parameter
is not specified then compiler of Java 9 will auto infers the type.
Before Java 7, we need to explicitly mention type on both side of the declaration statement.
60
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
With the introduction of empty diamond operator <> from Java 7, we need not mention type
on the right side of the declaration statement. We can leave empty inside the diamond operator.
Java compiler automatically determines the type on the right side of the declaration statement.
But, that rule doesn’t apply to anonymous inner classes. Empty diamond operator cannot be
used with anonymous inner classes.
For example, the below code gives compile time error if we run it in Java 7 environment.
61
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
{
abstract void add(T t1, T t2);
}
public class Java7DiamondOperator
{
public static void main(String[] args)
{
//Compile time error
//'<>' cannot be used with anonymous classes
Addition<Integer> integerAddition = new Addition<>() {
@Override
void add(Integer t1, Integer t2)
{
System.out.println(t1+t2);
}
};
}
}
This issue has been resolved from Java 9
Diamond Operator : From Java 9
From Java 9, We can use empty diamond operator <> for anonymous inner classes also. The
above code doesn’t show any compile time errors if we execute it in Java 9 environment.
abstract class Addition<T>
{
abstract void add(T t1, T t2);
}
62
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
Local Variable Type Inference, introduced in Java 10, allows us to declare local variables
without explicitly stating the type. Instead, we use the var keyword, and the compiler infers the
63
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
type of the variable based on the initializer expression. By using var, we don't need to use types
such as int, float, etc. It automatically infers the type of the variable based on the assigned
value. This feature is known as Local Variable Type Inference or LVTI in Java. This feature
is also sometimes referred to as "var declarations."
// Java 9 or older
int a = 10;
// Java 10 or higher
var a = 10;
Note: It is required to initialize the variable during declaration otherwise the compiler can't
infer the type and will generate an error.
Syntax and Examples
Here’s how we can use local variable type inference with the var keyword:
64
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
Switch Expressions
In Java, switch expressions are an enhancement of the traditional switch statement, introduced
in Java 12 (preview) and made standard in Java 14. Switch expressions allow us to use switch
in a more concise and expressive way, especially when returning a value.
Using switch expressions, we can return a value and we can use them within statements like
other expressions. We can use case L -> label to return a value or using yield, we can return a
value from a switch expression.
Switch Expressions are actually two enhancements that can be used independently but also
combined:
Like all expressions, switch expressions evaluate to a single value and can be used in
statements.
They may contain "case L ->" labels that eliminate the need for break statements to prevent fall
through.
65
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
In traditional switch statements, we have multiple case labels and potentially a default label.
With switch expressions, we can use the switch keyword followed by a switch expression block
{ ... }.
Each case is handled using -> (arrow operator) to specify the actions to take for each case.
Java provides a new way to return a value from a switch expression using case L - > notation.
Syntax
int dayOfWeek = 3;
66
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
};
In this example:
• Depending on its value, the corresponding case label (1, 2, ..., 7) determines the value
of dayName.
Switch Expression Using "case L:" Statements and the yield Statement
In java, we can get a value from switch expression and switch statement using yield statement.
yield statement returns the value and finishes the switch execution.
Syntax
case label1:
yield value;
67
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
In Java switch expressions, the yield keyword is used to return a value from a case block. This
is different from the break statement used in traditional switch statements, which terminates
the switch block without returning a value. The yield statement is specifically designed for
switch expressions, allowing them to produce a result.
• When a case block matches the switch expression's value, and the block contains
a yield statement, the expression following yield is evaluated.
• The result of this evaluation becomes the value of the entire switch expression.
• The yield statement also terminates the execution of the switch expression, preventing
fall-through to subsequent case blocks.
The yield keyword is essential for switch expressions as it enables them to return values,
making them more functional and expressive compared to traditional switch statements.
1. New Syntax to declare switch case labels: Case Label -> Expression
2. From Java 12, switch block can have multiple case labels separated by commas if they have
same set of statements to be executed.
3. From Java 12, we can return a value from switch block either through arrow operator (->)
or through break statements.
4. As switch block can return a value from Java 12, switch statements became switch
expressions.
68
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
5. Mixing of -> and : not allowed within a switch block. We should either use old method of
declaring case labels with colon (:) or new method of declaring case labels with arrow
operator (->) but not both.
6. break statement is optional if we use : to declare case labels. But, break statement is not
allowed if we are using -> for case labels.
2. Arrow (->) Syntax: The arrow (->) separates the case label from the expression to be
evaluated when that case is matched.
3. Yield Keyword: Starting from Java 13, the yield keyword was introduced to explicitly
return a value from a switch branch. For example:
default -> {
yield "Unknown";
};
import java.util.Scanner;
69
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
System.out.println("Enter score");
default -> {
yield result;
};
System.out.println(grade);
70
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
4. Multiple Constants in Case Labels: We can use multiple constants in a single case
label, separated by commas (case 1, 2, 3 -> ...).
5. Default Case: The default case is optional, but recommended to handle unexpected
values and ensure program reliability.
import java.util.*;
71
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
};
System.out.println(message);
What's Different?
Text blocks
Before text blocks, multiline strings were typically written using concatenation or escape
sequences:
72
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
" <body>\n" +
" </body>\n" +
"</html>\n";
System.out.println(html);
Text blocks, introduced as a preview feature in Java 13 and became a standard feature in Java
15, provide a more readable way to write multi-line strings in Java. They are particularly useful
for embedding blocks of HTML, SQL, JSON, or any other format that traditionally requires
escaping and concatenation.
In Java, text blocks are denoted by triple double quotes """ and allow for:
• Multi-line Strings: Text blocks can span multiple lines without needing escape
characters like \n.
• Embedded Quotes: Quotes (") within the text can be used without escaping.
73
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
<html>
<body>
<p>Hello, Java!</p>
</body>
</html>
""";
System.out.println(html);
1. Whitespace Management: Leading and trailing blank lines and spaces are excluded
from the string content, allowing for cleaner formatting.
2. Escape Sequences: Escape sequences such as \n for newlines and \" for double quotes
are interpreted as literals within the text block.
3. Expressions: We can embed expressions inside text blocks using ${...}. These
expressions are evaluated at runtime and their results are concatenated into the string.
Hello, ${name}!
""";
4. Raw String Literals: Text blocks are also known as raw string literals because they
treat backslashes (\) and other escape sequences literally, simplifying the representation
of strings that contain backslashes.
74
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
Records
Records are a new feature introduced in Java 14 that provide a compact way to declare classes
whose main purpose is to store data. They are a form of transparent carrier for immutable data,
offering a concise syntax and built-in support for getters, equals(), hashCode(), and toString()
methods. Records streamline the creation of simple data-holding classes, promoting
immutability and enhancing code readability.
Syntax of a Record
A record is declared using the record keyword, followed by the record name and a list of
components (fields). Here’s a simple example:
In this example:
• Records are implicitly final and immutable, meaning their components cannot be
changed after initialization.
75
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
Sealed classes
Sealed classes, introduced in Java 17, are a new type of class that restricts which other classes
or interfaces can extend or implement them.
In Java, the sealed keyword is used to control and restrict the inheritance hierarchy of classes
and interfaces. It allows us to specify which classes or interfaces are allowed to extend or
implement a particular class or interface.
We can declare classes and interfaces using the sealed keyword in Java. The sealed keyword is
not considered as a modifier, but rather a way to control the inheritance relationships within a
hierarchy. We can use it at the class or interface level, not for methods or fields.
Here's an example:
public sealed class Shape permits Circle, Rectangle, Triangle {
// Class definition
}
76
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
In this example:
• sealed class Shape declares Shape as a sealed class.
• permits Circle, Rectangle, Triangle specifies that only classes Circle, Rectangle, and
Triangle can directly extend Shape.
Example:
public sealed class Account permits SavingsAccount, FixedDepositAccount { }
In this example, the Account class is declared as a sealed class with permits SavingsAccount,
and FixedDepositAccount, specifying that only these two classes can extend it. As a result, no
other classes (like CheckingAccount) can extend Account beyond the ones specified in the
permits clause. Each specific account type extends Account and can add its own methods and
properties specific to that account type.
To declare a sealed interface in Java, we use the sealed keyword along with the permits
clause to specify which classes or interfaces are allowed to implement/extend it.
public sealed interface InterfaceName permits Class1, Interface1, ... {
// Interface methods and constants
}
77
Ajay Kumar Garg Engineering College, Ghaziabad
Information Technology Department
78