0% found this document useful (0 votes)
23 views

unit 3 java

A functional interface in Java contains exactly one abstract method and is the basis for lambda expressions and method references. Java 8 introduced lambda expressions for cleaner code, while the Stream API allows functional-style processing of collections. Additionally, features like default methods, static methods, and new language constructs such as records and sealed classes were introduced in later Java versions to enhance functionality and maintainability.
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)
23 views

unit 3 java

A functional interface in Java contains exactly one abstract method and is the basis for lambda expressions and method references. Java 8 introduced lambda expressions for cleaner code, while the Stream API allows functional-style processing of collections. Additionally, features like default methods, static methods, and new language constructs such as records and sealed classes were introduced in later Java versions to enhance functionality and maintainability.
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/ 14

What is a Functional Interface?

A functional interface in Java is an interface that contains exactly one abstract method. It can have
any number of default or static methods, but only one abstract method.

Functional interfaces are the basis for lambda expressions and method references in Java.

Characteristics:

• Can have multiple default and static methods.

• Annotated with @FunctionalInterface (optional but recommended).

• Used by built-in Java functional APIs (Predicate, Function, etc.)

Example: Custom Functional Interface

@FunctionalInterface

interface Calculator {

int calculate(int a, int b);

Common Built-in Functional Interfaces (from java.util.function)

Interface Method Purpose

Predicate<T> boolean test(T) Boolean test for filtering

Function<T,R> R apply(T) Converts T to R

Consumer<T> void accept(T) Performs action on T

Supplier<T> T get() Supplies a value

UnaryOperator<T> T apply(T) Operates on and returns same type

BinaryOperator<T> T apply(T,T) Combines two of same type

What is a Lambda Expression in Java?

A lambda expression in Java is a concise way to represent an instance of a functional interface. It


enables you to write inline implementations of interfaces with a single abstract method — making
your code cleaner and more readable.

Lambda expressions were introduced in Java 8 to support functional programming.

Syntax of Lambda Expression

(parameters) -> expression

Or for multiple statements:

(parameters) -> {
// multiple statements

return result;

Example: Basic Lambda with Custom Functional Interface

Step 1: Define a Functional Interface

@FunctionalInterface

interface MyOperation {

int operate(int a, int b);

Step 2: Use Lambda to Implement It

public class Main {

public static void main(String[] args) {

MyOperation addition = (a, b) -> a + b;

MyOperation subtraction = (a, b) -> a - b;

System.out.println("Add: " + addition.operate(10, 5)); // Output: 15

System.out.println("Subtract: " + subtraction.operate(10, 5)); // Output: 5

3. Method References – A Shorter Form of Lambda

A method reference is a shorthand for a lambda expression that calls a method.

Syntax:

ClassName::staticMethod

instance::instanceMethod

ClassName::new // constructor reference

Example:

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

// Using lambda

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


// Using method reference

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

When to Use:

• When lambda just calls a method

• Improves readability

4. Stream API – Functional Style Processing of Collections

The Stream API lets you perform complex operations on data collections (like List, Set, etc.) in a
declarative, functional manner.

Common Stream Operations:

Operation Type Description

filter Intermediate Filters elements based on predicate

map Intermediate Transforms elements

sorted Intermediate Sorts elements

collect Terminal Gathers results into a collection

forEach Terminal Performs action for each element

Full Example: Using Everything Together

import java.util.*;

import java.util.stream.*;

public class Main {

public static void main(String[] args) {

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

// Using Stream, Lambda, and Method Reference

names.stream()

.filter(name -> name.startsWith("A")) // Lambda

.map(String::toUpperCase) // Method reference

.forEach(System.out::println); // Method reference


}

▶ Output:

ALICE

AMANDA

1. Default Methods in Interfaces

A default method is a method in an interface that has a body (implementation) and uses the default
keyword.

Purpose:

• Add new functionality to interfaces without breaking existing classes that implement them.

• Enables multiple inheritance of behavior (not state).

Syntax:

interface MyInterface {

default void show() {

System.out.println("Default show() method");

Example:

interface A {

default void sayHello() {

System.out.println("Hello from A");

class B implements A {

// Inherits sayHello() from A

public class Main {

public static void main(String[] args) {


B b = new B();

b.sayHello(); // Output: Hello from A

⚠ Note on Conflict:

If two interfaces provide default methods with the same signature, the implementing class must
override it.

interface A {

default void greet() { System.out.println("Hello from A"); }

interface B {

default void greet() { System.out.println("Hello from B"); }

class C implements A, B {

public void greet() {

System.out.println("Hello from C");

2. Static Methods in Interfaces

A static method in an interface is associated with the interface itself — not the instance — and
cannot be overridden.

Syntax:

interface Utility {

static void printMessage() {

System.out.println("Static method in interface");

}
Usage:

public class Main {

public static void main(String[] args) {

Utility.printMessage(); // Call through interface name

1. Statement

1. Base64 Encode and Decode

Definition:

Base64 encoding is a way to convert binary data (e.g., files, images, strings) into ASCII characters. It's
commonly used to transmit data over media that are designed to deal with textual data (e.g., email,
JSON, URLs).

Java 8 introduced the java.util.Base64 class to handle encoding and decoding operations.

2. forEach() Method

Definition:

The forEach() method is a default method introduced in Java 8 for the Iterable and Stream
interfaces. It allows performing an action on each element of a collection using lambda expressions
or method references.

Example with List:

import java.util.*;

public class Main {

public static void main(String[] args) {

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

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

names.forEach(System.out::println); // Using method reference

}
3. Try-With-Resources

Definition:

The try-with-resources statement (introduced in Java 7) automatically closes resources such as files,
sockets, or database connections once they are no longer needed. Resources must implement the
AutoCloseable interface.

This feature avoids manual closing and prevents memory leaks.

Example:

import java.io.*;

public class Main {

public static void main(String[] args) {

try (BufferedReader reader = new BufferedReader(new FileReader("example.txt"))) {

String line = reader.readLine();

System.out.println("File line: " + line);

} catch (IOException e) {

e.printStackTrace();

Type Annotations in Java

Definition:

Type annotations are annotations that can be applied wherever a type is used — not just on
declarations like classes, methods, or variables.

They were introduced in Java 8 (JSR 308) to enable stronger type checking and more powerful
compile-time and runtime analysis.

Why Use Type Annotations?

• To provide additional type information to tools, compilers, or frameworks

• Useful in static code analysis, null-checking, taint analysis, etc.

• Allows frameworks like Checker Framework, FindBugs, or SpotBugs to validate code more
deeply

Example:
➤ Define a custom type annotation:

• import java.lang.annotation.*;

• @Target(ElementType.TYPE_USE)
• @Retention(RetentionPolicy.RUNTIME)
• @interface NonNull {}

➤ Apply it:

• public class Demo {


• public @NonNull String getName() {
• return "ChatGPT";
• }

• public void printList(List<@NonNull String> names) {
• names.forEach(System.out::println);
• }

• public void checkCast(Object obj) {
• String str = (@NonNull String) obj;
• }
• }

The @NonNull annotation here means the object should not be null — tools
can catch violations.

Repeating Annotations in Java

Definition:

Repeating annotations allow you to apply the same annotation multiple times to
the same declaration or type use.

This feature was introduced in Java 8 to improve clarity and support use cases where
multiple values of the same annotation are needed.

How It Works:

1. You create a repeatable annotation using @Repeatable.


2. You define a container annotation that holds an array of the repeated
annotations.

Java Module System (Jigsaw Project)


Definition:

The Java Module System was introduced in Java 9 under the Jigsaw Project. It
allows you to define modules within your application, enabling better encapsulation,
modularization, and dependency management for large applications.

A module is a group of related packages that are treated as a single unit. It defines
its dependencies on other modules and can specify which of its packages should be
visible to other modules.

Why Use the Java Module System?

1. Modularization of large applications into smaller, manageable parts.


2. Improved security and encapsulation by exposing only the necessary parts of
your application.
3. Reduced complexity when managing dependencies.
4. Better performance by enabling the JVM to load only the necessary parts of an
application.

Anonymous Class in Java

Definition:

An anonymous class in Java is a class without a name that is used to instantiate and
define a class that implements an interface or extends a class on the fly, often in a
single expression. These are commonly used for one-time implementations of
classes or interfaces, especially when you don't need to create a separate named class.

Examples of Anonymous Classes

. Anonymous Class Implementing an Interface

One of the most common uses of anonymous classes is implementing interfaces


without creating a new named class.

public class Main {

public static void main(String[] args) {

// Anonymous class implementing Runnable interface

Runnable runnable = new Runnable() {


@Override

public void run() {

System.out.println("Hello from the anonymous class!");

};

Thread thread = new Thread(runnable);

thread.start();

Explanation:

Here, an anonymous class implements the Runnable interface and overrides the run()
method.

We don't need to create a separate Runnable implementation class; instead, we define


it inline where it's needed.

Diamond Syntax with Inner Anonymous Class

The diamond syntax (<>) in Java was introduced in Java 7 and provides a simpler
way to define the type of a generic object without needing to specify the type on both
sides of the assignment. It works by letting the compiler infer the type on the right-
hand side based on the left-hand side.

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

Local Variable Type Inference (Java 10)

Definition:

Local variable type inference was introduced in Java 10 with the var keyword. It
allows the compiler to infer the type of a local variable based on the context,
eliminating the need to explicitly specify the variable's type. The type of the variable
is determined at compile-time and cannot be changed later.
In simpler terms, you can use var to let the compiler figure out the type of a variable,
improving code readability and reducing verbosity

var message = "Hello, Java 10!"; // Inferred type: String

var count = 10; // Inferred type: int

Introduction to Switch Expressions:

The switch statement has been a part of Java since its early versions, but Java 12
introduced switch expressions, which enhance the traditional switch statement by
providing more flexibility, conciseness, and safety.

Switch expressions allow returning a value from a switch block, using multiple
labels for a case, and provide the option for fall-through behavior to be controlled
more easily.

Before Java 12, the switch statement was used only for executing statements based
on a variable’s value, but now with switch expressions, you can return a value
directly from the switch.

Basic Example of Switch Expression:

public class Main {

public static void main(String[] args) {

int day = 3;

String result = switch (day) {

case 1 -> "Monday";

case 2 -> "Tuesday";

case 3 -> "Wednesday";

case 4 -> "Thursday";

case 5 -> "Friday";

case 6 -> "Saturday";

case 7 -> "Sunday";

default -> "Invalid day";

};
System.out.println(result); // Output: Wednesday

yield Keyword in Java (Switch Expressions)

Introduction to yield Keyword:

The yield keyword was introduced in Java 12 alongside switch expressions. It is


used within a switch expression to return a value from a case block. Unlike the
traditional switch statement, which doesn't allow returning a value directly, switch
expressions allow returning values, and yield is the mechanism that makes it possible
when more than one statement is needed in a case block.

Basic Example Using yield:

public class Main {

public static void main(String[] args) {

int day = 3;

String result = switch (day) {

case 1 -> "Monday"; // Single statement, return immediately

case 2 -> "Tuesday"; // Single statement, return immediately

case 3 -> {

// Multiple statements

String message = "Wednesday is the middle of the week!";

yield message; // Return value using yield

default -> "Invalid day";

};

System.out.println(result); // Output: Wednesday is the middle of the week!


}

Text Blocks in Java (Java 13)

Introduction to Text Blocks:

Introduced in Java 13, Text Blocks provide a more readable and convenient way to
work with multi-line strings. Before text blocks, multi-line strings in Java were often
cumbersome and required handling escape sequences like \n for new lines and \t for
indentation. Text blocks offer a cleaner, more intuitive syntax for defining multi-line
string literals.

Basic Syntax of Text Blocks:

Text blocks in Java are defined using triple double quotes """, which allows for multi-
line strings to be written directly in the source code.

String textBlock = """

This is a

multi-line string

using text blocks.

""";

Records in Java (Java 14)

Introduction to Records:

Introduced in Java 14 as a preview feature and finalized in Java 16, records are a
special kind of class in Java designed to model immutable data in a compact and
concise way. Records simplify the creation of data-carrier classes by automatically
generating several useful methods (e.g., toString(), equals(), hashCode(), and getters)
based on the fields of the class.

In traditional Java, for classes that were just used to hold data, developers had to
manually write boilerplate code for constructors, getters, toString(), equals(), and
hashCode() methods. With records, this repetitive task is automated, leading to more
readable and maintainable code.

🔹 Sealed Classes in Java (Java 15)


✅ Introduction to Sealed Classes:

Sealed classes were introduced in Java 15 as a preview feature and finalized


in Java 17. They allow developers to restrict which other classes or interfaces can
extend or implement them. This concept provides more control over the inheritance
hierarchy, improving security, maintainability, and predictability of the code.

A sealed class is a class that cannot be subclassed freely. Instead, you can define a
set of specific subclasses (or implementers) that are allowed to extend or implement
the sealed class.

✅ Declaring a Sealed Class:

To declare a class as sealed, you use the sealed modifier followed by


the permits keyword, which lists the allowed subclasses.

public sealed class Shape permits Circle, Rectangle {


// Common functionality for all shapes }
public final class Circle extends Shape {
// Circle-specific code }
public final class Rectangle extends Shape {
// Rectangle-specific code }

You might also like