0% found this document useful (0 votes)
4 views80 pages

Java New Features-Unit-3[1]

The document outlines new features in Java, including functional interfaces, lambda expressions, and the Stream API, as well as a timeline of Java's evolution from its initial release in 1996 to recent updates. It explains key concepts like lambda expressions, functional interfaces, default methods, and static methods, along with examples and benefits of these features. Additionally, it introduces switch expressions, the yield keyword, and text blocks for improved code readability and maintainability.

Uploaded by

rajatmaurya7906
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)
4 views80 pages

Java New Features-Unit-3[1]

The document outlines new features in Java, including functional interfaces, lambda expressions, and the Stream API, as well as a timeline of Java's evolution from its initial release in 1996 to recent updates. It explains key concepts like lambda expressions, functional interfaces, default methods, and static methods, along with examples and benefits of these features. Additionally, it introduces switch expressions, the yield keyword, and text blocks for improved code readability and maintainability.

Uploaded by

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

Unit-3

Topics to be covered:

Java New Features: Functional Interfaces, Lambda


Expression, Method References, Stream API, Default
Methods, Static Method, Base64 Encode and Decode,
ForEach Method, Try-with-resources, Type Annotations,
Repeating Annotations, Java Module System, Diamond
Syntax with Inner Anonymous Class, Local Variable Type
Inference, Switch Expressions, Yield Keyword, Text Blocks,
Records, Sealed Classes 1
Java Evolution Timeline
Java Version Year Key Features Introduced
Java 1.0 1996 Initial release, introduction of applets
Java 2 1998 Introduction of Swing, Collections framework
Java 5 2004 Generics, annotations, enumerations, varargs
Java 7 2011 Diamond operator, try-with-resources, switch on strings
Java 8 2014 Lambda expressions, Stream API, Date and Time API
Java 9 2017 Module System (Project Jigsaw)
Java 10 2018 Local-variable type inference
New String methods, local-variable syntax for lambda
Java 11 2018
parameters
Java 12–17 2019–2021 Continuous improvements and introduction of new features

2
Java 8 Features
Oracle released a new version of Java as Java 8 in March 18, 2014. It was a revolutionary release of the Java for
software development platform. It includes various upgrades to the Java programming, JVM, Tools and
libraries.
•Lambda expressions,
•Method references,
•Functional interfaces,
•Stream API,
•Default methods,
•Base64 Encode Decode,
•Static methods in interface,
•Optional class,
•Collectors class,
•ForEach() method,
•Nashorn JavaScript Engine,
•Parallel Array Sorting,
•Type and Repeating Annotations,
•IO Enhancements,
•Concurrency Enhancements,
•JDBC Enhancements etc.
3
Lambda Expressions
Lambda expressions were introduced in Java 8 and represent a significant
enhancement to the Java programming language. A lambda expression
provides a clear and concise way to represent one method interface using
an expression.
Definition: A lambda expression in Java is a short block of code which takes
in parameters and returns a value. Lambda expressions are similar to
methods, but they do not need a name and can be implemented right in
the body of a method.
•Not having any name
•Not having any return type
•Not having any modifier
Syntax:(parameters) -> { statements; } or(parameters) -> expression;
4
Normal Function Lambda Expression

public void add(int a, int b) { (int a, int b) -> {


System.out.println(a + b); System.out.println(a + b); }
} Or more concisely:
(a, b) -> { System.out.println(a + b);
}

Steps to Convert a Function into a Lambda Expression


1.Delete modifier
2.Delete return type
3.Delete name of method
4.Put arrow (->)
5
Benefits of Lambda Expression
Conciseness:Lambda expressions help to reduce the amount of boilerplate
code.They eliminate the need for anonymous classes and provide a more
compact and readable syntax.
Readability: By reducing boilerplate, lambda expressions make the code more
readable and easier to maintain.
Functional Programming: They enable functional programming in Java, which
can lead to cleaner and more efficient code.
Parallel Processing: They work seamlessly with Java Stream API, which can
leverage multi-core architectures for parallel processing.
6
Functional Interface in Java

• It is an interface that contains exactly one abstract method.


• can have any number of default or static methods without affecting its
functional interface status.
• It can invoke lambda expressions.

Key Characteristics:

• Single Abstract Method: This method can be implemented using a lambda


expression.

• @Functional Interface Annotation: used to declare an interface as


functional. This annotation is optional but helps to prevent accidental
addition of more abstract methods.
7
• Default and Static Methods : A functional interface can have any number of
default or static methods without affecting its functional interface status.
• Inheritance : If an interface inherits another interface that is functional and
does not declare additional abstract methods, it remains a functional
interface.

Example: Functional Interface Declaration


@FunctionalInterface
interface MyFunctionalInterface {
void myMethod();
}

8
@FunctionalInterface annotation:
• @FA is used to confirm that this interface is a functional interface and it will
definitely have one abstract method.
• If remove @FIA, and already given static or default method but not abstract
method, then it will give an exception. Because Functional interface can have
static, default method but also have abstract method. Also must not have
more than one abstract method.
• It can have any number of static, default method but only one abstract
method.
@FunctionalInterface
interface MyFunctionalInterface {
static void myMethod() {
System.out.println("hello");
}
}
// Give error as found no abstract method 9
Valid example of a functional interface
@FunctionalInterface
interface MyFunctionalInterface {
void abrst(); // Single Abstract Method (Required)

static void myMethod() {


System.out.println("hello"); // Static method (Allowed)
}

default void felo() {


System.out.println(); // Default method (Allowed)
}
}

This is a valid functional interface, because it: Has only one abstract method. Includes static and default
methods, which are allowed.
10
Example of using a functional interface with a lambda expression:
A FA can be implemented using Lambda expression which provides a concise way to represent an instance of the object.

@FunctionalInterface This interface is a functional interface because


interface MyFunctionalInterface { it has exactly one abstract method. The
void myMethod(); // Abstract method @FunctionalInterface annotation is optional
} but helpful for compiler validation.
public class FIEx {
public static void main(String[] args) {
MyFunctionalInterface instance = () -> System.out.println("Hello, world!");
instance.myMethod(); // Lambda expression is invoked here
} } //Output → Hello, world!
 Lambda Expression: () -> System.out.println("Hello, world!") Represents the
implementation of the abstract method myMethod().
 () means it takes no parameters. The expression after -> is the method body. 11
This lambda expression can have more than one statement, for this, we will have to put
them in this bracket: { } . Example:
@FunctionalInterface
interface MyFunctionalInterface {
void myMethod();
}
public class FunctionalInterfaceExample {
public static void main(String[] args) {
// Lambda expression to implement myMethod
MyFunctionalInterface instance = () -> {
System.out.println("Hello, world!");
System.out.println("Hello, world!");
};

// Calling the method


instance.myMethod();
} } 12
Default Method
A default method is a method defined inside an interface that provides a default
implementation. This allows the interface to provide a method that can be used by
implementing classes without forcing them to override it.
• Defined in Interface: The method is part of an interface (not a class or abstract class).
Syntax includes the keyword default.

• Has a Default Implementation: Unlike abstract methods, it has a method body. So, it provides a default
behavior.

• Optional to Override: Classes that implement the interface can use the default method as-is, or Override it
if different behavior is needed.

public interface MyInterface {


Syntax: void abstractnethod();
default void defaultmethod() {
System.out.println("Default method from interface");
} } 13
@FunctionalInterface
interface Vehicle { When to Use @FunctionalInterface:
void startEngine(); // Abstract method - must be overridden
• Use it only when: The interface has
// Default method - optional to override exactly one abstract method.
default void honk() {
System.out.println("Honking the horn!"); • It’s meant for lambda expressions
} }

public class Car implements Vehicle {


@Override
public void startEngine() {
System.out.println("Starting car engine...");
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.startEngine(); // Outputs: Starting car engine...
car.honk(); // Outputs: Honking the horn!
} } 14
Multiple Inheritance and Default Methods

"When a class implements multiple interfaces with default methods,


conflicts can arise if the interfaces contain methods with the same
signature. In such cases, the implementing class must resolve the
conflict by overriding the method."
Problem:
If a class implements two interfaces that both define a default method with the
same signature (same method name and parameters), the compiler doesn't
know which one to use and there will be a conflict.
Solution to resolve the conflict:
The class must
i) override the method & provide its own implementation.
ii) or change the method signature as given in next slides.
15
1) Output:
interface InterfaceA { InterfaceA default method
default void defaultMethod() {
System.out.println("InterfaceA default method");
InterfaceB default method
} } MyClass implementation

interface InterfaceB { Both interfaces define a defaultMethod()


default void defaultMethod() { using the default keyword. When a class
System.out.println("InterfaceB default method"); implements both interfaces with the same
} }
default method signature, Java requires the
public class MyClass implements InterfaceA, InterfaceB {
@Override method to be overridden to resolve the
public void defaultMethod() { conflict.
InterfaceA.super.defaultMethod(); - Inside MyClass, we use
InterfaceB.super.defaultMethod(); InterfaceA.super.defaultMethod() and
System.out.println("MyClass implementation");
InterfaceB.super.defaultMethod() to
}
public static void main(String[] args) { invoke both versions.
MyClass myClass = new MyClass(); - Finally, the class adds its own
myClass.defaultMethod(); implementation print statement.
} } 16
2) Modified Program with Parameter in InterfaceB
interface InterfaceA {
default void defaultMethod() { public static void main(String[] args)
System.out.println("InterfaceA default method"); {
} } MyClass myClass = new
interface InterfaceB { MyClass();
default void defaultMethod(int a) {
System.out.println("InterfaceB default method myClass.defaultMethod();
with argument: " + a); // Calls overridden method from MyClass
} }
public class MyClass implements InterfaceA, myClass.defaultMethod(10);
InterfaceB { // Calls InterfaceB's default method with parameter
} }
@Override
public void defaultMethod() {
InterfaceA.super.defaultMethod();
Next=>
System.out.println("MyClass implementation");
} 17
InterfaceA defines defaultMethod() (no arguments).
InterfaceB defines defaultMethod(int a) — a method with a different
signature.T his is not a conflict, so no need to override both. MyClass overrides
only the no-argument method. In main, we call both versions: the overridden
one and the parameterized one from InterfaceB.

Output: InterfaceA default method


MyClass implementation
InterfaceB default method with argument: 10

18
Static Method
• A method that belongs to the class rather than any instance of the class.
• Ie. We can call a static method without creating an object of the class.
• Static methods are defined using the static keyword & static methods cannot be
overridden by subclasses.
public interface MyInterface {
static void staticMethod() {
System.out.println("Static method in interface."); } }

public class StaticMethodInInterfaceExample {

public static void main(String[] args) {


MyInterface.staticMethod(); // Outputs: Static method in interface.
} }
19
Key Characteristics of Static Methods

Class-Level Method: Static methods belong to the class itself, not to


any specific object instance.
No this Reference: Since static methods are not associated with
instances, they cannot use the this keyword.
Can Access Static Data: Static methods can access and modify static
variables directly.
Cannot Access Instance Data Directly: Static methods cannot
access instance variables or instance methods directly. They must
use an object reference to do so.

Static method cannot be overridden but we can override default method.


20
SWITCH EXPRESSIONS

Definition: Switch expressions extend the traditional switch statement,


allowing it to be used as an expression that returns a value.

Purpose: Simplifies code and enhances readability.

• Before Java 12: Only switch statements existed.

• Java 12 and Beyond: Introduction of switch expressions as a preview


feature; finalized in Java 14.

21
Traditional ‘switch’ Statement

int day = 2; Limitations of Traditional switch:


String dayName;
switch (day) { Wordy: Requires writing break statements for
case 1: each case.
dayName = "Sunday"; Error-Prone: Easy to forget break, which can
break; cause mistakes.
case 2: Not Flexible: Cannot be used directly in
dayName = "Monday"; expressions to return values.
break;
// Other cases
default:
dayName = "Invalid day";
break;
} 22
Switch Expression Syntax Example 2 (Block Form):
String result = switch (day) {
Example 1 (Expression Form):
case 1 -> "Sunday";
String dayName = switch (day) { case 2 -> {
case 1 -> "Sunday"; System.out.println("Second day of the
case 2 -> "Monday"; week");
// Other cases yield "Monday";
default -> "Invalid day"; }
}; };
// We can return value using yield keyword

• Arrow Token (->): Used instead of : and break


• Expression Form: Directly returns a value.
• Block Form: Can contain multiple statements (requires yield to return a23value).
yield Keyword
String result = switch (value) {
1. yield is used inside block-form case 1 -> "One"; // Simple expression form
case 2 -> {
switch expressions to return a System.out.println("More than one");
value from a case block. yield "Two"; // block form — yield is
2. It allows multiple statements needed here
}
inside a case block, and returns a default -> "Unknown";
value using yield. };

• Use -> for single-line results (no yield needed).


• Use { ... yield ...; } when you have multiple statements in a case.
• return is used in methods.
• yield is used in switch expressions, to return a value from a case block only.
24
The yield keyword in Java is used within switch expressions
(not traditional switch statements) to return a value from a
case block.
This is especially useful when you need to:
• Perform multiple operations inside a case block
• Return a computed result
It helps in making code more readable and organized.
Benefits :
• Less Boilerplate Code: No need for explicit break statements in every
case.
• More Readable: Easier to understand, maintain, and debug.
Code=>>25
Program1(Switch Expression): Program2 (To write multiple statements & return
values in switch expression) :
int day = 3;
String dayName = switch (day) int day = 2;
String result = switch (day) {
{
case 1 -> "Sunday";
case 1 -> "Sunday"; case 2 -> {
case 2 -> "Monday"; System.out.println("Second day of the week");
case 3 -> "Tuesday"; yield "Monday";
case 4 -> "Wednesday"; }
case 5 -> "Thursday"; case 3 -> {
int length = "Tuesday".length();
case 6 -> "Friday";
yield "Tuesday (" + length + " letters)";
case 7 -> "Saturday"; }
default -> "Invalid day"; default -> "noDay";
}; };
System.out.println(dayName); System.out.println(result);
//O/P: Tuesday 26
TEXT BLOCKS

Definition: A text block is a multiline string literal that enhances the readability
and ease of writing strings that span multiple lines.
Purpose: Simplifies writing and maintaining multiline strings.
Before Java 13:Multiline strings required concatenation or escape sequences.
Java 13 and Beyond:Text blocks were introduced to simplify multiline string
handling.
• Opening and Closing: Use triple double
Syntax of Text Blocks
quotes (""") to start and end a text block.
Example: • Newlines: Preserved as-is, so line breaks
String textBlock = """ appear in the string exactly as written.
This is a text block. • Indentation: Leading whitespace is ignored,
It spans multiple lines. which makes formatting easier and cleaner.
It is easy to read and maintain.
"""; Used within HTML, JSON, SQL, or any multiline content directly inside Java
27
code.
Traditional Way vs. Text Block

Traditional Way:
• Requires manual \n for line breaks.
String query = "SELECT * FROM users\n" + • Needs string concatenation (+) for
"WHERE age > 25\n" + multiline formatting.
"ORDER BY name;"; • Harder to read and maintain.

Text Block (Java 13+):


String query = """ o No need for \n or +.
SELECT * FROM users o Much cleaner and more readable.
WHERE age > 25 o Preserves the original layout, making SQL, HTML, or JSON
ORDER BY name; easier to write.
""";

28
Java program demonstrating the use of text blocks

public class tbl {


Code for Traditional approach for multiline strings:
public static void main(String[] args) {

public class tbl { String textBlock = """


public static void main(String[] args) { This is a text block.
It spans multiple lines.
String textBlock = "This is a text block.\n"
+ "It spans multiple lines.\n" +
It is easy to read and maintain.
"It is easy to read and maintain."; """;
System.out.println(textBlock);
System.out.println(textBlock); } }
}} // The content inside the text block
preserves line breaks and formatting
exactly as written. 29
Local Variable Type Inference
• It allows the compiler to determine (or infer) the type of a local variable based on the
assigned value, using the var keyword.
• Purpose: Simplifies code by eliminating the need to explicitly declare variable types.
• Syntax: var varname=value
Before Java 10: Explicit type declaration required for all variables. Ex:
String message = "Hello!";
int number = 10;
Java 10 and beyond: Introduction of var keyword for local variable declarations.
Ex: var message = "Hello!"; // compiler infers this is a String
var number = 10; // compiler infers this is an int
• var is not a type; it just tells the compiler to infer the type.
• Only for local variables (inside methods, loops, etc.).
• Cannot be used for:
• Method parameters
• Class-level fields 30
Benefits of Local Variable Type Inference
• Conciseness: Reduces boilerplate code.
• Readability: Makes code easier to read by focusing on variable names & values.
• Maintenance: Simplifies code maintenance by reducing redundancy.

Limitations of Local Variable Type Inference


• Readability: Can reduce readability if overused or used inappropriately.
• Explicit Typing: In some cases, explicit typing can make code more
understandable.
• Type Clarity: Less clear what type a variable is, especially for complex or less
familiar types.

31
Records
• Records are a special kind of class in Java designed to model
immutable data carriers with a fixed set of fields.
• Used when want to store data in java that not needed to be
changed.
• Purpose: Provide a concise syntax to create data classes with
automatic implementations of common methods.
Features/ key benefits of Records
• Immutable: Fields are final and cannot be changed once set.
• Concise Syntax: Reduces boilerplate code for data classes.
• Automatic Methods: Generates constructor, getter, equals(),
hashCode(), and toString() methods.(these were to be created in
traditional classes). 32
Comparison between a traditional Java class and a Java record
Traditional Class
public class Person { Record
private final String name;
private final int age; record Person(String name, int age) { }
public Person(String name, int age) {
this.name = name;
this.age = age; }
public String getName() { return name; }
public int getAge() { return age; }
@Override
public boolean equals(Object o) {
return false; }
@Override
public int hashCode() {
return age; }

@Override
public String toString() {
return name;
}} 33
Key Differences:

Feature Traditional Class Record


Boilerplate code Requires manual writing Auto-generated
Constructor Must be defined Auto-defined
Auto-defined as component
Getters Must be written
names
equals() / hashCode() Must override manually Auto-implemented
toString() Must override manually Auto-implemented
Mutability Can be mutable Immutable by default
Cannot extend classes (can
Inheritance Can extend other classes
implement interfaces) 34
Syntax and Examples

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


Example: record Person(String name, int age) { }
Person person = new Person(name: "Avi", age: 14);
Automatic Method Implementations
Equals: HashCode( HC is used to get function ID):
Person p1 = new Person("Avi", 14); int hashCode = p1.hashCode();
Person p2 = new Person("Avi", 14);
boolean isEqual = p1.equals(p2); // true

ToString(Get data in structured format):

String personString = p1.toString(); // Person[name: "Avi", age: 14] 35


Traditional code:

public class Person {


private final String name; private final int age;
public Person(String name, int age) {
this.name = name; public static void main(String[] args) {
this.age = age; } Person per = new Person(name: "Krishna", age: 21);
public String getName() { return name; } System.out.println(per.getName());
public int getAge() { return age; }
System.out.println(per.hashCode());
@Override System.out.println(per);
public boolean equals(Object o) { }
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name,
person.name); }
@Override
public int hashCode() { Output:
return Objects.hash(name, age); }
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}"; } } 36
The code below demonstrates the use of a record class in Java. Record classes were
introduced in Java 14 (as a preview) and became a standard feature in Java 16.
public class RecordExample {
public record Person(String name, int age){ }
public static void main(String[] args) {
Person person = new Person("Krishna", : 21); // Creating instance of record
// Accessing fields
System.out.println("Name: " + person.name());
System.out.println("Age: " + person.age());
System.out.println(person.hashCode()); // Accessing hashCode
// Automatically generated toString method
System.out.println(“Person Details:”+person);
Person p2 = new Person("Avi", 14);
// Equality Check
System.out.println(“Are the two person equal?”);
p1.equals(p2);
}}
/*This code assumes that the Person class is declared as a record like this:
public record Person(String name, int age) {} */
37
Java automatically generates:

Constructor — new Person("Krishna", 21)


Getter methods — name() and age()
equals() method — Compares both name and age fields
hashCode() method — Based on name and age
toString() method — Returns something like
Person[name=Krishna, age=21]

38
Sealed Classes
It is a class that limits or restricts which other classes can extend it.
In simple words, we decide which specific subclasses are allowed.

• Sealed classes were introduced in Java 15 (as a test/preview


feature) and finalized in Java 17 (use them officially).
Features of Sealed Classes

• Restricted Hierarchies: Only specific classes can extend a sealed class.


• Enhanced Control: Helps in maintaining a well-defined class structure.
• Combines with Permits: Uses the permits keyword to specify allowed
subclasses.
39
Syntax: public sealed class Shape permits Circle, Rectangle {}
// The Shape class can only be extended by Circle and Rectangle.
Subclasses can be final, non-sealed or sealed
Final: The subclass cannot be extended further.
Sealed: The subclass can be further restricted with specified permitted subclasses.
Non-Sealed: The subclass can be extended by any other class.

public sealed class Shape permits Circle, Rectangle ,


Polygon{}
final class Circle extends Shape { Example with
// Circle implementation } sealed, non-
final class Rectangle extends Shape { sealed, and
final, next
// Rectangle implementation }
slide=>
40
// 1. Sealed superclass // 3. Non-sealed subclass: can be extended furthe
public sealed class Shape permits Circle, Square public non-sealed class Square extends Shape {
{ @Override
public void draw() { public void draw() {
System.out.println("Drawing a shape."); System.out.println("Drawing a square.");
} }
} }

// 2. Final subclass: cannot be extended further // 4. Extending the non-sealed subclass


public final class Circle extends Shape { class ColoredSquare extends Square {
@Override @Override
public void draw() { public void draw() {
System.out.println("Drawing a circle."); System.out.println("Drawing a colored
} square.");
} }
}
41
// 7. Main class to test the implementation
// 5. Sealed subclass: further restricts subclassing public class Main {
public sealed class Polygon extends Shape permits Triangle public static void main(String[] args) {
{ Shape c = new Circle();
Shape s = new Square();
@Override Shape cs = new ColoredSquare();
public void draw() { Shape t = new Triangle();
System.out.println("Drawing a polygon.");
} c.draw(); // Drawing a circle.
} s.draw(); // Drawing a square.
cs.draw(); // Drawing a colored square.
t.draw(); // Drawing a triangle.
// 6. Final subclass of sealed Polygon } }
public final class Triangle extends Polygon {
Each object (Circle, Square, etc.) is a subclass of
@Override Shape. By assigning them to a variable of type Shape,
public void draw() { we can treat all these different shapes the same way
System.out.println("Drawing a triangle."); — and still get their specific behavior when we call
methods like draw().
}
} 42
Diamond Syntax with Inner Anonymous Class
Inner Anonymous Class: it is an inner class without a name and for
which only a single object is created.
An anonymous inner class can be useful when making an instance of
an object with certain “extras” such as overriding methods of a class
or interface, without having to actually subclass a class.
Syntax: an anonymous class expression is like the invocation of a
constructor, except that there is a class definition contained in a
block of code.
Anonymous inner classes are generic created via below listed two
ways as follows:
• Class (may be abstract or concrete)
• Interface 43
Types of Anonymous Inner Class
Based on declaration and behavior, there are 3 types of anonymous
Inner classes:
1. AIC that extends a class
2. AIC that implements an interface
3. AIC that defines inside method/constructor argument
Test t = new Test()
{
// data members and methods
public void test_method()
{
........
........ } }; 44
Diamond syntax
Definition: Diamond syntax also known as Diamond operator (<>)
allows the compiler to infer the type arguments of a generic class,
reducing boilerplate code.
Introduced as a new feature in Java SE 7.The purpose of diamond
operator is to avoid redundant code by leaving the generic type in the
right side of the expression.
• Before Java 7, we have to explicitly mention generic type in the
right side as well.
• List<String> myList = new ArrayList<String>();
• Since Java 7, no need to mention generic type in the right side,
instead we can use diamond operator. Compiler can infer type.
• List<String> myList = new ArrayList<>(); 45
1- Ex: Without Generic 2- Example: Diamond Operator + Anonymous
Inner Class
abstract class Calculator { // Abstract class with generic type <T>
abstract int add(int a, int b); } abstract class Calculator<T> {
abstract T add(T a, T b); // abstract method implemented by subclass
public class Main { }
public static void main(String[] args) {
Calculator obj = new Calculator() { public class Main {
int add(int a, int b) { public static void main(String[] args) {
return a + b; } };
// Using anonymous inner class with generic type Integer+ diamond operator
int result = obj.add(10, 20); Calculator<Integer> obj = new Calculator<>() {
System.out.println("Sum is: " + result); }} // Implementing the abstract method and override it
Integer add(Integer a, Integer b) {
return a + b;
• Calculator is an abstract class. } };
• Making object by anonymous inner class and
override the add() method which is used to add // Calling the method
two numbers Integer result = obj.add(50, 70);
System.out.println("Sum is: " + result);
} }
46
Introduction to ForEach Method
• The forEach method, introduced in Java 8, is a powerful way to
iterate over elements of a collection and performing an action on
each element of the collection.

Syntax: collection.forEach(action); where

collection: The collection to iterate over. (list, map etc)


action: The action to be performed for each element (typically a
lambda expression).

47
Example:
List<String> list = Arrays.asList("A", "B", "C");
// Arrays.asList() is a static method in Java's java.util.Arrays class. Its purpose is:"To convert an array into a
list"
list.forEach(element -> System.out.println(element));
//This code prints each element in the list using a lambda expression. Here element is an
iterator that goes to each item in the list and then print. Example: forEach
Traditionally, it work like this: import java.util.Arrays;
for(String element : list) { import java.util.List;
System.out.println(element); public class ForEachExample {
} public static void main(String[] args) {
List<String> items = Arrays.asList("Apple", "Banana",
"Cherry", "Date");
Output: // Using forEach with a lambda expression
Apple Fruits items.forEach(item -> System.out.println(item + "
Banana Fruits
Cherry Fruits
Fruits"));
Date Fruits }} 48
Base64 encoding and decoding

Base64 encoding is a method of converting binary data into a textual format.


This is especially useful when you need to:
• Transmit data over text-based protocols (like HTTP)
• Store binary data (like images or files) in databases or JSON/XML
Java Support for Base64: For this it provides a utility class=>> java.util.Base64
This class contains static methods to:
•Encode binary data into Base64 string
•Decode Base64 string back to binary
Common use cases:
•Email (MIME) for attachments
•Storing binary data in XML/JSON
•Encoding URLs with binary or unsafe characters 49
Encoding vs Decoding
Base64 Encoding → Converts binary data to a Base64 string (text format)
Base64 Decoding → Converts Base64 string back into binary data

import java.util.Base64;
Example: public class Base64Example {
public static void main(String[] args) {
// Step 1: Original String
String original = "Hello Java!";
// Step 2: Encode the String using Base64
String encoded = Base64.getEncoder().encodeToString(original.getBytes());
System.out.println("Encoded String: " + encoded);
// Step 3: Decode the Base64 string back to original
byte[] decodedBytes = Base64.getDecoder().decode(encoded);
String decoded = new String(decodedBytes);
System.out.println("Decoded String: " + decoded);
}
}
50
Step-by-Step Explanation:

🔹 Step 1: Original String


String original = "Hello Java!";We declare a normal string that we want to encode.This could be anything: a password,
message, file content, etc.
Step 2: String encoded = Base64.getEncoder().encodeToString(original.getBytes());What happens
here:original.getBytes()Converts the string into a byte array (since Base64 works with binary
data).Base64.getEncoder()Creates a Base64 encoder object..encodeToString(...)Encodes the byte array to a Base64-
encoded string. ️ Output Example:arduinoCopyEditEncoded String: SGVsbG8gSmF2YSE=🔹 Step 3:
DecodingjavaCopyEditbyte[] decodedBytes = Base64.getDecoder().decode(encoded);String decoded = new
String(decodedBytes);What happens here:Base64.getDecoder()Creates a Base64 decoder
object..decode(encoded)Converts the Base64 string back into its original byte array.new String(decodedBytes)Converts
that byte array back into the original readable string. ️ Output Example:arduinoCopyEditDecoded String: Hello Java!🔹
Step 4: Print ResultsjavaCopyEditSystem.out.println("Encoded String: " + encoded);System.out.println("Decoded String: " +
decoded);Prints both the Base64 string and the decoded original string.

51
try-with-resources
• is a try statement that is used for declaring one or more
resources such as streams, sockets, DBs, connections,
etc.
• also referred as "Automatic Resource Management and it
was introduced in Java7.
• It simplifies resource handling by automatically closing
resources after they are no longer needed, preventing
try(resources declarations) {
resource leaks and reducing //boilerplate code.
use of the resources
Syntax:
}
catch(Exception e) {
// exception handling
} 52
Main Points:

• Class used in try-with-resources statement should implement


AutoCloseable interface and the close() method of it gets invoked
automatically at runtime.
• More than one class can be declared in try-with-resources stmt.
• Declared multiple classes in the try block are closed in reverse
order.
• Except the declaration of resources within the parenthesis
everything is the same as normal try/catch block of a try block.
• Resource declared in try gets instantiated just before the start of
the try-block.
53
Example 1: With single resource

import java.io.FileReader;
import java.io.IOException;
public class Try_withDemo {
public static void main(String args[]) {
try(FileReader fr = new FileReader(“D://file1.txt")) {
char [] a = new char[50];
fr.read(a); // reads the contentto the array
for(char c : a)
System.out.print(c); // prints the characters one by one
} catch (IOException e) {
e.printStackTrace();
} } }
54
Example 2: Try with Resources having Multiple Resources
{
import java.io.BufferedReader;
String line;
import java.io.FileReader;
while ((line =
import java.io.FileWriter;
bufferedReader.readLine()) != null)
import java.io.PrintWriter;
{
import java.io.IOException; // Read content line by line and write it
public class Main { to the output (file2.txt) file
public static void main(String[] args) { printWriter.println(line);
// try block with multiple resources }
try ( System.out.println("Content
FileReader fileReader = new FileReader("file1.txt"); copied.");
BufferedReader bufferedReader = new } catch (IOException e) {
BufferedReader(fileReader); e.printStackTrace();
FileWriter fileWriter = new FileWriter("file2.txt"); } } }
PrintWriter printWriter = new PrintWriter(fileWriter)
)
55
Annotations
• Are special kinds of metadata added to Java code. They provide
information to the compiler or JVM but do not change the actual logic
of the program.
• They are always prefixed with @, like @Override.
Use of Annotations:
• To give instructions to the compiler.
• To help tools and frameworks (like Spring, Hibernate).
• To provide runtime information.
• Commonly used for configuration, Documentation and Debugging
Common Built-in Annotations in Java
1. @Override(Indicates that a method overrides a method from a superclass)
2. @Deprecated(Marks a method or class as outdated)
3. @ SuppressWarnings(Tells the compiler to ignore warnings.) 56
Ex. @Deprecated
class Animal {
void sound() { class OldCode {
Ex.
@Override System.out.println("Animal @Deprecated
sound"); } } void oldMethod() {
System.out.println("Old method");
class Dog extends Animal { }
@Override }
void sound() {
System.out.println("Bark"); } }

import java.util.*;
Ex. class Example {
@SuppressWarnings
@SuppressWarnings("unchecked")
void myMethod() {
List list = new ArrayList(); // unchecked warning suppressed
}}
57
Type Annotations
Definition: Type annotations are annotations that can be applied to
any use of a type, including type declarations, type casts, and type
parameters.
Get introduced in Java 8, allow annotations to be used anywhere a
type is used.
Purpose: Provide additional information to the compiler and tools to
improve code quality and enable advanced features.

58
public class TypeAnnotationExample {
public void process(@NonNull String input) {
// Method implementation
}}

List<@NonNull String> names = new ArrayList<>();

@NonNull is a type annotation which tells that values of input or names must
not be null. Provide additional information to the compiler and tools to improve
code quality and enable advanced features.

59
Program on custom @NonNull annotation
(We do null check manually because annotation do not check themselves, we have to check manually)

import java.lang.annotation.*; import java.util.ArrayList;


import java.util.List; import java.lang.reflect.*;
// Step 1: Create a custom NonNull annotation
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@interface NonNull { }
// Step 2: Use the annotation in a class
public class TypeAnnotationExample {
// Method with @NonNull parameter
public void process(@NonNull String input) {
if (input == null) {
throw new IllegalArgumentException("Input must not be null!"); }
System.out.println("Input received: " + input); }
Cont… 60
public static void main(String[] args) throws Exception {
TypeAnnotationExample obj = new TypeAnnotationExample();

// Step 3: List with @NonNull generic type


List<@NonNull String> names = new ArrayList<>();
names.add("Abhi");
names.add(“Avi");

// Print names (manual null-check )


for (String name : names) {
if (name == null) {
System.out.println("Null value found!");
} else {
obj.process(name); // Call method
} } } }
61
Repeating Annotation
• Normally, we can use an annotation only once.
• Repeating annotations, introduced in Java 8, allow the same annotation to be
applied multiple times to the same declaration or type use. This feature simplifies
scenarios where we need to apply the same annotation with different values.
• For this, we have to mark it as @Repeatable.
@Repeatable(Schedules.class)
public @interface Schedule {
String day(); • @Repeatable: Indicates that the annotation
String time(); }
can be repeated.
public @interface Schedules {
Schedule[] value(); • Schedules: A container annotation that holds
} an array of Schedule annotations.
@Schedule(day = "Monday", time = "9am")
@Schedule(day = "Wednesday", time = "1pm") - Declare a repeatable annotation type
public void plan() { - Declare the containing annotation type
// Method implementation } 62 Cont..
import java.lang.annotation.*;
import java.lang.reflect.Method; // Step 4: Use reflection to read and display annotations
public static void main(String[] args) throws Exception {
// Step 1: Declare the repeatable annotation Planner planner = new Planner();
@Repeatable(Schedules.class) Method method = planner.getClass().getMethod("plan");
@interface Schedule {
String day(); Program 1: // Get all Schedule annotations applied to the method
String time(); Schedule[] schedules =
} method.getAnnotationsByType(Schedule.class);

// Step 2: Declare the container annotation System.out.println("Schedules for 'plan' method:");


@interface Schedules { for (Schedule schedule : schedules) {
Schedule[] value(); System.out.println("- Day: " + schedule.day() + ", Time: "
} + schedule.time());
}
// Step 3: Apply the repeatable annotation to a method }
public class Planner { }

@Schedule(day = "Monday", time = "9am")


@Schedule(day = "Wednesday", time = "1pm")
public void plan() {
// Method implementation
System.out.println("Planning sessions scheduled.");
} 63
Program 2:
import java.lang.annotation.*; public class SimpleRepeat {
// 1. Make Repeatable annotation public static void main(String[] args) {
@Repeatable(Hints.class)
@interface Hint { // to take all @Hint that are upon Class
String value(); } Hint[] hints =
// 2. Container annotation (to hold repeatable) SimpleRepeat.class.getAnnotationsByType(Hint.class);
@Retention(RetentionPolicy.RUNTIME) // to print all Hint
@interface Hints { for (Hint h : hints) {
Hint[] value(); } System.out.println(h.value());
// 3. Use repeating annotation on class } } }
@Hint("First hint")
@Hint("Second hint")
Step-by-Step Explanation:
1. @interface Hint – Created a custom annotation called Hint, which has a value() method.
2. @Repeatable(Hints.class) – This means that the @Hint annotation can be used multiple times. It will use the Hints
container to hold all the repeated @Hint annotations.
3. @interface Hints – Created a container annotation named Hints that holds an array of Hint annotations.
4. Two @Hint annotations are applied to the class (SimpleRepeat). One with "First hint" and the other with "Second hint".
5. getAnnotationsByType() is used to retrieve all the @Hint annotations applied to the class.
6. The loop iterates over the annotations and prints their values ("First hint" and "Second hint").
64
Method References
• Method references are a shorthand syntax for calling a method by referring to it with the
help of its class directly.
• They make the code more readable and concise, especially when used with functional
interfaces and lambda expressions.
• Definition: Method references provide a way to refer to methods without invoking them.
They can be used to point to a method by name instead of calling it directly.
• Benefits: Improves code readability and reduces boilerplate code.

Types of Method References


1. Static Method References
Syntax: ClassName::staticMethodName
2. Instance Method References of a Particular Object
Syntax: instance::instanceMethodName
3. Constructor References
Syntax: ClassName::new
65
1. Static Method References Example
public class Name {
static void Example(String s) {
System.out.println(s); }

public static void main(String[] args) {


List<String> demo = Arrays.asList("Apple", "Banana", "Cherry", "Date");
demo.forEach(Name::Example); // Method reference to static method
} }

• The static method Example(String s) prints a string.


• In main(), a list of strings is created.
• demo.forEach(Name::Example) uses a static method reference using class name “Name” to
print each string in the list.
• Instead of writing a lambda like s -> Name.Example(s), we directly reference the method.
66
2. Instance Method References of a Particular Object
public class Name {
void Example(String s) {
System.out.println(s); }
public static void main(String[] args) {
Name obj = new Name(); // Create an instance of the class
List<String> demo = Arrays.asList("Apple", "Banana", "Cherry", "Date");
demo.forEach(obj::Example); // Instance method reference
} }

1. A class Name has an instance method Example(String s) which prints a string.


2. In main(), we create an object obj of class Name.
3. A list of strings is created.
4. demo.forEach(obj::Example) uses an instance method reference of the particular object
obj.
5. Instead of using a lambda like x -> obj.Example(x), we directly refer to the method.
67
3. Constructor References
We can refer to a constructor by using the new keyword. Here, we are referring to a
constructor with the help of a functional interface.
@FunctionalInterface
interface Messageable {
Message getMessage(String msg); //method named getMessage
1. Messageable is a functional
takes one argument: a String named msg, Returns an object of type Message,
declaration inside a functional interface.
interface with one abstract
method: getMessage(String
} msg).
class Message {
Message(String msg) { 2. The Message class has a
constructor that takes a string
System.out.println(msg); } } and prints it.
public class ConstructorReference {
public static void main(String[] args) {
Messageable hello = Message::new; //“Assign the
constructor of the Message class (that takes a String argument) to the
functional interface Messageable.”
hello.getMessage("Hello"); } } 68
Stream API
Definition:
• The Stream API, introduced in Java 8, provides a powerful and efficient way
to process sequences of elements.
• It supports functional-style operations on streams of elements such as:
Filtering, Mapping, Reducing
Purpose:To efficiently process large collections of data.

Use ofStream API:


Conciseness: allow complex operations in fewer lines.
Readability: chainable and declarative expressions.
Performance: Optimizes data processing using lazy evaluation, which means
intermediate operations are only executed when needed.

69
Traditional way and the Stream API approach to iterate over a list:

Traditional Way: With Stream API:

List<String> items = Arrays.asList("apple",


"banana", "cherry"); List<String> items = Arrays.asList("apple",
for (String item : items) { "banana", "cherry");
System.out.println(item); items.stream().forEach(System.out::println);
}

70
Example: Create a Stream and Print Each Element

import java.util.Arrays;
import java.util.List;

public class StreamExample {


public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "cherry");

// Create a stream and print each item


fruits.stream().forEach(System.out::println);
}
}

71
Three simple ways to create a Stream
1. Create Stream from a List
List<String> items = Arrays.asList("apple", "banana", "cherry");
Stream<String> list2 = items.stream(); //Use .stream() method from a List.

2. Create Stream from an Array


String[] group = {"String1", "String2", "String3"};
Stream<String> list3 = Arrays.stream(group); //Use Arrays.stream(array) to convert an array into a Stream.

3. Create Stream using Stream.of()


Stream<Integer> list4 = Stream.of(1, 2, 3, 4); // Use Stream.of() to create a Stream directly from values.

import java.util.Arrays;
Import: import java.util.List;
import java.util.stream.Stream; 72
Stream Operations
Stream Operations are divided into two categories:

1- Intermediate Operations: These return a stream, enabling method chaining


(i.e., multiple operations can be connected in a chain).They are lazy, meaning
they do not perform any processing until a terminal operation is invoked.
Examples:
• filter – filters elements based on a condition.
• map – transforms each element.
• sorted – sorts the stream elements.
2. Terminal Operations :
These produce a result and close the stream, triggering the actual processing of data.
Examples:
• forEach – performs an action for each element.
• collect – collects elements into a collection (like a List or Set).
• reduce – reduces the stream to a single value (e.g., sum, max). 73
A) Program on Filter Operation Intermediate Operations Programs
Filter: Selects elements based on a predicate ie. a condition

List<String> items = Arrays.asList("apple", "banana", "cherry", "date");


List<String> result = items.stream() // Converts the list to a stream.
.filter(s -> s.startsWith("a")) // “For every string s in the stream, include it in the
result only if it starts with 'a'.
.collect(Collectors.toList()); // Collects the result back into a list.
System.out.println(result); // Output: [apple]

B) Program on Map Operation


List<String> items = Arrays.asList("apple", "banana", "cherry");
List<String> result = items.stream()
.map(String::toUpperCase) // Each element (a string) is transformed using the method toUpperCase().
.collect(Collectors.toList());
System.out.println(result); // Output: [APPLE, BANANA, CHERRY]
74
C) Program on sorted operation
(Sorts elements based on a comparator)

List<String> items = Arrays.asList("banana", "apple", "cherry");


List<String> result = items.stream() //Converts the list to a stream.
.sorted() // Sorts the elements in natural order (alphabetical for strings).
.collect(Collectors.toList());

System.out.println(result); // Output: [apple, banana, cherry]

75
Terminal Operations Programs
A) ForEach Operation: Performs an action for each element of the stream.
List<String> items = Arrays.asList("apple", "banana", "cherry");
items.stream()
.forEach(System.out::println);
B) collect operation: accumulate elements into a collection (like a List, Set, or Map).:
List<String> items = Arrays.asList("apple", "banana", "cherry");
List<String> result = items.stream()
.collect(Collectors.toList());
System.out.println(result); // Output: [apple, banana, cherry]

C) reduce operation: to combine elements into a single result.


List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream() // Converts the list into a stream.
.reduce(0, Integer::sum); //0 is the identity value (initial value for the sum), It adds all elements:
0 + 1 + 2 + 3 + 4 + 5 = 15.
System.out.println(sum); // Output: 15 76
Java Module System
It is a mechanism to group related packages and resources together
into a module, which explicitly declares what it exports and what it
requires. It improves the structure, maintainability, and
encapsulation of Java applications.
Java 9 introduced the Java Module System to enhance code for:
• Modularity, Encapsulation
• Dependency Management: Explicitly declares dependencies, avoiding
runtime issues

java --list-modules
This command lists all the Java Platform built-in Modules available in the current JDK.
https://www.youtube.com/watch?v=M4Ek1qHTky4
77
Key Concepts

Keyword Meaning
module Declares a module
requires Specifies dependencies on other modules
exports Makes a package accessible to other modules

module-info.java Special file that contains the module declaration

78
Directory Structure:
Module A: (Provides a service) - moduleA/module-info.java
module moduleA {
exports com.a; }

moduleA/com/a/HelloA.java
package com.a;

public class HelloA {


public void sayHello() {
System.out.println("Hello from Module A!");
}
}

79
Module B: (Depends on Module A) moduleB/com/b/MainB.java
package com.b;
moduleB/module-info.java
import com.a.HelloA;
module moduleB {
requires moduleA; public class MainB {
} public static void main(String[] args) {
HelloA obj = new HelloA();
Compilation and Execution obj.sayHello();
} }

1. javac -d out/moduleA moduleA/module-info.java moduleA/com/a/HelloA.java


2. javac --module-path out -d out/moduleB moduleB/module-info.java
moduleB/com/b/MainB.java

Run the program: java --module-path out -m moduleB/com.b.MainB


80
Output: Hello from Module A!

You might also like