TOPIC 01

Introduction to Object-Oriented Programming

๐ŸŒ What is OOP and Why Was It Introduced?

Object-Oriented Programming (OOP) is a programming paradigm that organizes software design around data (objects) rather than around functions and logic. Before OOP, developers used Procedural / Structured Programming โ€” writing code as a sequence of instructions executed top to bottom.

๐Ÿ“Œ Paradigm means a style or way of thinking
Java supports multiple paradigms โ€” procedural (inside methods), functional (lambdas), and fully object-oriented (classes, inheritance, etc.). But at its core, Java is an OOP language.

The Problem with Procedural Programming

Procedural programming worked fine for small programs in the 1970s. As software grew larger, serious problems emerged:

PROCEDURAL PROGRAMMING PROBLEMS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ main() โ”‚ โ”œโ”€โ”€ function_A() โ”€โ”€โ”€โ”€ uses global variable X โ”‚ โ”œโ”€โ”€ function_B() โ”€โ”€โ”€โ”€ also uses global variable X โ† DANGER โ”‚ โ””โ”€โ”€ function_C() โ”€โ”€โ”€โ”€ modifies global variable X โ† CHAOS Problem 1: No data protection โ€” any function can read/write any variable Problem 2: Spaghetti code โ€” functions call each other freely, hard to trace Problem 3: Hard to reuse โ€” logic tied to specific data, not portable Problem 4: Hard to scale โ€” adding features breaks existing code Problem 5: Hard to model reality โ€” real world has objects, not functions

OOP was introduced to solve all five of these problems by organizing code around objects that bundle both data and behavior together.

๐Ÿ’ก Real World Analogy
Think of a car. A car has state (color, fuel level, speed) and behavior (accelerate, brake, refuel). You don't need to know how the engine combustion works to drive โ€” you just interact with the interface (steering wheel, pedals). That's exactly how OOP works.

๐Ÿ›๏ธ The Four Pillars of OOP

Every OOP language is built on four fundamental concepts. Java implements all four:

๐Ÿงฑ
Encapsulation
Wrapping data and methods into a single unit (class). Hiding internal data from outside. Achieved via private + getters/setters.
๐ŸŽญ
Abstraction
Hiding complex implementation, showing only what's necessary. Achieved via abstract classes and interfaces.
๐Ÿงฌ
Inheritance
A class acquiring properties and methods of another class. Promotes code reuse. Achieved via extends.
๐ŸŽจ
Polymorphism
One name, many behaviors. Same method acts differently in different contexts. Achieved via overloading & overriding.
๐Ÿ“Œ Memory Trick โ€” APIE
Remember the four pillars as A.P.I.E. โ€” Abstraction, Polymorphism, Inheritance, Encapsulation

๐Ÿ“ฆ Object โ€” The Core Concept

An object is an instance of a class. Every real-world entity can be modeled as an object. An object has two things:

OBJECT = STATE + BEHAVIOR โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Real World: Java Object: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Person class Person { } โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ State: โ”‚ โ”‚ Instance Variables: โ”‚ โ”‚ name โ”‚ โ†’ โ”‚ String name; โ”‚ โ”‚ age โ”‚ โ”‚ int age; โ”‚ โ”‚ height โ”‚ โ”‚ double height; โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ Behavior: โ”‚ โ”‚ Methods: โ”‚ โ”‚ eat() โ”‚ โ†’ โ”‚ void eat() { } โ”‚ โ”‚ walk() โ”‚ โ”‚ void walk() { } โ”‚ โ”‚ talk() โ”‚ โ”‚ void talk() { } โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โš”๏ธ OOP vs Procedural โ€” Full Comparison

FeatureProceduralOOP (Java)
FocusFunctions / proceduresObjects & classes
DataGlobal, shared freelyEncapsulated inside objects
Code ReuseThrough function callsThrough inheritance
SecurityNo data hidingData hiding via private
ComplexityHarder to manage large appsScales well with size
Real-world mappingPoorNatural โ€” everything is an object
ExamplesC, Pascal, BASICJava, Python, C++, C#

โ˜• Why Java for OOP?

Java was designed from the ground up as an OOP language by James Gosling at Sun Microsystems (1995). Key reasons Java is ideal for OOP:

  • Pure OOP enforcement โ€” every piece of code lives inside a class
  • Platform independence โ€” Write Once, Run Anywhere via JVM
  • Strong type system โ€” catches errors at compile time
  • Rich standard library โ€” entire Java API is built with OOP
  • Industry adoption โ€” Android, Spring Boot, enterprise backends
โš ๏ธ Common Misconception
Java is NOT 100% pure OOP. It has 8 primitive data types (int, double, boolean, etc.) that are NOT objects. Languages like Smalltalk are truly 100% OOP. Java is often called a "strongly OOP" language, not a "pure OOP" language. Wrapper classes (Integer, Double) exist to bridge this gap.

๐Ÿ”„ How Java Code Runs โ€” JVM Overview

JAVA EXECUTION FLOW โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ You write: MyClass.java (Source Code) โ”‚ โ–ผ javac compiler โ†’ MyClass.class (Bytecode) โ”‚ โ–ผ JVM (Java Virtual Machine) โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Class Loader โ†’ loads .class file โ”‚ โ”‚ Bytecode Verifier โ”‚ โ”‚ Interpreter + JIT Compiler โ”‚ โ”‚ โ†’ Converts bytecode to machine code โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ–ผ Machine Code runs on your OS (Windows/Mac/Linux) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ .java โ†’ (javac) โ†’ .class โ†’ (JVM) โ†’ runs anywhere!

๐Ÿ”ฌ Demonstrating OOP Thinking in Java

Let's contrast how you'd solve the same problem in a procedural style vs an OOP style in Java.

โŒ Procedural Approach (Not Recommended)

ProceduralStyle.java
// โŒ Procedural style โ€” data is scattered as loose variables
public class ProceduralStyle {

    // Data floating around โ€” no protection, any method can touch it
    static String studentName = "Rahul";
    static int studentAge = 20;
    static double studentMarks = 88.5;

    static void printStudent() {
        System.out.println("Name: " + studentName);
        System.out.println("Age: " + studentAge);
        System.out.println("Marks: " + studentMarks);
    }

    public static void main(String[] args) {
        printStudent();
        // Problem: What if we need 2 students? We'd need 6 more variables!
        // This does NOT scale at all.
    }
}
โ–ถ OUTPUT
Name: Rahul Age: 20 Marks: 88.5

โœ… OOP Approach (Recommended)

Student.java
// โœ… OOP style โ€” data and behavior bundled inside a class
public class Student {

    // State โ€” instance variables
    String name;
    int age;
    double marks;

    // Behavior โ€” method
    void printInfo() {
        System.out.println("Name: " + name + ", Age: " + age + ", Marks: " + marks);
    }
}

class Main {
    public static void main(String[] args) {

        // Create first student object
        Student s1 = new Student();
        s1.name = "Rahul";
        s1.age = 20;
        s1.marks = 88.5;

        // Create second student object โ€” completely independent!
        Student s2 = new Student();
        s2.name = "Priya";
        s2.age = 22;
        s2.marks = 94.0;

        s1.printInfo();
        s2.printInfo();

        // Scales to 1000 students with just 1 more line each!
    }
}
โ–ถ OUTPUT
Name: Rahul, Age: 20, Marks: 88.5 Name: Priya, Age: 22, Marks: 94.0

โœ… Demonstrating All 4 Pillars in One Program

FourPillarsDemo.java
// Demonstrates all 4 OOP pillars conceptually

// ABSTRACTION โ€” hide implementation via abstract class
abstract class Shape {
    abstract double area();            // contract โ€” every Shape MUST have area()
    void describe() {
        System.out.println("I am a shape with area: " + area());
    }
}

// INHERITANCE โ€” Circle inherits from Shape
class Circle extends Shape {

    // ENCAPSULATION โ€” private field + getter
    private double radius;

    Circle(double radius) { this.radius = radius; }

    public double getRadius() { return radius; }

    // POLYMORPHISM โ€” overrides parent's area()
    @Override
    double area() { return Math.PI * radius * radius; }
}

class Rectangle extends Shape {
    private double width, height;
    Rectangle(double w, double h) { width = w; height = h; }

    @Override
    double area() { return width * height; }
}

public class FourPillarsDemo {
    public static void main(String[] args) {
        // POLYMORPHISM โ€” same type (Shape), different actual objects
        Shape[] shapes = { new Circle(5), new Rectangle(4, 6) };

        for (Shape s : shapes) {
            s.describe();  // area() called depends on actual type at runtime
        }
    }
}
โ–ถ OUTPUT
I am a shape with area: 78.53981633974483 I am a shape with area: 24.0
โš ๏ธ Common Mistake โ€” Confusing Class and Object
A class is just a template โ€” it takes no memory. An object is a real instance โ€” it takes memory in the Heap. You cannot call instance methods or access instance variables without creating an object first.
๐Ÿ’ก Best Practice
Always name your classes with nouns (Student, Car, BankAccount) and methods with verbs (getName, calculateTax, printInfo). This naturally leads to good OOP design where classes represent things and methods represent actions.
โญ Must-Know Interview Topics for OOP Intro
4 pillars with definitions + real examples ยท OOP vs Procedural ยท Java is not 100% pure OOP ยท Real-world analogies ยท Benefits of OOP
Q1. What is Object-Oriented Programming? Why was it introduced?
OOP is a programming paradigm that organizes code around objects โ€” entities that combine data (state) and behavior (methods). It was introduced to overcome the limitations of procedural programming: poor data security (no encapsulation), difficulty scaling large codebases, inability to model real-world entities naturally, and lack of code reuse mechanisms. OOP solves these via its four pillars: Encapsulation, Abstraction, Inheritance, and Polymorphism.
Q2. What are the four pillars of OOP? Explain each in one line.
Encapsulation โ€” Bundling data and methods together and hiding internal state (private fields + getters/setters).
Abstraction โ€” Hiding complex implementation details and exposing only necessary functionality (abstract class, interface).
Inheritance โ€” A child class acquiring the properties and behaviors of a parent class (extends keyword).
Polymorphism โ€” One interface, many implementations โ€” same method name behaves differently (overloading + overriding).
Q3. Is Java 100% Object-Oriented? Why or why not?
No, Java is NOT 100% pure OOP. The reason is that Java has 8 primitive data types (int, long, double, float, char, boolean, byte, short) that are not objects โ€” they're stored directly in memory without any class wrapper. In a truly pure OOP language (like Smalltalk), everything is an object. Java provides wrapper classes (Integer, Double, etc.) to treat primitives as objects when needed, but the primitives themselves are not objects. Additionally, Java has static methods and variables that belong to the class rather than to any object.
Q4. What is the difference between a Class and an Object?
A Class is a blueprint or template โ€” it defines what properties and behaviors objects of that type will have. It exists only in source code and takes no memory at runtime.

An Object is a concrete instance created from the class. It exists at runtime in the Heap memory and has its own copy of instance variables.

Analogy: Class = cookie cutter mold. Object = the actual cookie.
Q5. What is the difference between Abstraction and Encapsulation? (Most confused!)
This is one of the most commonly confused OOP questions.

Encapsulation = How you protect data โ€” hiding the internal state of an object and exposing it only through controlled getters/setters. It's about data hiding. Example: A bank account class has a private balance field โ€” you can't directly modify it, only through deposit() or withdraw().

Abstraction = What you hide โ€” hiding complex implementation logic and showing only the essential interface. It's about implementation hiding. Example: You call list.sort() โ€” you don't know whether it uses merge sort or quicksort internally. That detail is abstracted away.

Simple summary: Encapsulation hides data. Abstraction hides complexity.
Q6. What are the advantages of OOP over Procedural Programming?
1. Data Security โ€” Encapsulation protects data from unauthorized access.
2. Code Reusability โ€” Inheritance lets you reuse existing code without rewriting.
3. Maintainability โ€” Changes in one class don't break unrelated classes (if designed well).
4. Scalability โ€” Large applications are easier to manage with well-defined classes.
5. Real-world modeling โ€” Objects mirror real-world entities naturally.
6. Flexibility โ€” Polymorphism allows writing flexible and extensible code.
Q7. Tricky: Can you achieve OOP concepts in a non-OOP language like C?
Yes, conceptually you can simulate OOP in C using structs (for encapsulation), function pointers (for polymorphism), and careful code organization (for abstraction and inheritance). The Linux kernel is largely written in C with OOP-like patterns. However, the language doesn't enforce or support these natively โ€” you have to implement them manually. This is why OOP languages like Java are preferred: they enforce these patterns at the language level.

๐ŸŒฑ How OOP Concepts Drive Spring Boot

Spring Boot is built entirely on OOP principles. Understanding OOP is a prerequisite to understanding Spring. Here's how each pillar maps to Spring Boot:

โœ… OOP Pillars in Spring Boot
Encapsulation โ†’ Every Spring Bean (component) hides its internal state. You inject dependencies, not raw data.
Abstraction โ†’ You write to interfaces (UserRepository), not implementations. Spring injects the concrete class at runtime.
Inheritance โ†’ Spring controllers often extend base controllers for shared error handling, CORS config, etc.
Polymorphism โ†’ Spring's @Qualifier and profiles let you swap implementations without changing calling code.

Spring Bean โ€” OOP in Action

UserService.java โ€” Spring Boot
// ABSTRACTION โ€” program to interface, not implementation
public interface UserService {
    User findById(Long id);
    List<User> findAll();
}

// ENCAPSULATION โ€” implementation hidden inside service class
@Service  // Spring annotation โ€” marks this as a managed Bean
public class UserServiceImpl implements UserService {

    // ENCAPSULATION โ€” private dependency, injected by Spring
    private final UserRepository userRepository;

    // Constructor injection โ€” recommended OOP approach
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public User findById(Long id) {
        return userRepository.findById(id).orElseThrow();
    }

    @Override
    public List<User> findAll() {
        return userRepository.findAll();
    }
}

// POLYMORPHISM โ€” Controller uses UserService interface
// Spring decides at runtime which implementation to inject
@RestController
public class UserController {

    private final UserService userService;  // Interface reference!

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }
}

Notice: UserController depends on the interface UserService, not the implementation UserServiceImpl. This is Abstraction + Polymorphism in action. You can swap the entire implementation (e.g., from SQL to NoSQL) without touching the controller.

Key Spring annotations to know: @Service @Repository @Controller @Component @Autowired

โญ Must-Know Facts for Exams
  • OOP was introduced to overcome limitations of procedural programming
  • The 4 pillars: Encapsulation, Abstraction, Inheritance, Polymorphism (mnemonic: APIE)
  • Java is NOT 100% pure OOP (8 primitive types are not objects)
  • Smalltalk is considered a 100% pure OOP language
  • Java was created by James Gosling at Sun Microsystems in 1995
  • JVM = Java Virtual Machine โ€” enables platform independence
  • Class takes no memory; Object takes memory in Heap

๐Ÿ“ Quick MCQ Traps

QuestionAnswerWhy (Trap)
Which is NOT a pillar of OOP? Compilation The 4 pillars are Encapsulation, Abstraction, Inheritance, Polymorphism only
Is Java 100% OOP? No Primitive types (int, char etc.) are not objects
What does a class represent? Blueprint / Template Not a real entity โ€” it's a design. Object is the real entity
Which OOP pillar prevents direct data access? Encapsulation Common confusion โ€” Abstraction hides complexity, Encapsulation hides data
Which pillar allows one class to use methods of another? Inheritance Not to be confused with Composition (has-a relationship)
In which memory area are objects stored in Java? Heap Stack stores references and primitive locals. Heap stores objects

๐Ÿ“– One-Liner Definitions (for quick revision)

  • OOP โ€” A programming paradigm organizing code around objects that bundle data and behavior.
  • Class โ€” A blueprint that defines the structure and behavior of objects.
  • Object โ€” A runtime instance of a class with its own state in Heap memory.
  • Encapsulation โ€” Bundling data and methods; hiding data via access modifiers.
  • Abstraction โ€” Hiding implementation details; exposing only the interface.
  • Inheritance โ€” A subclass acquiring properties and methods of a superclass.
  • Polymorphism โ€” Same method name behaving differently based on context or object type.
  • JVM โ€” Java Virtual Machine that interprets bytecode and enables cross-platform execution.
TOPIC 02

Classes & Objects

๐Ÿงฑ What is a Class?

A class is the fundamental building block of OOP in Java. It is a logical construct โ€” a blueprint or template that describes what properties (data) and behaviors (methods) objects of that type will have.

Think of a class as an architectural blueprint for a house. The blueprint itself is not a house โ€” it doesn't consume land or materials. But from one blueprint you can construct many houses, each with its own address, color, and residents.

๐Ÿ“Œ Key Point โ€” Class takes NO memory
When you write a class definition, no memory is allocated in the JVM. Memory is only allocated when you create an object using the new keyword. The class is purely a compile-time concept.

Anatomy of a Class

CLASS STRUCTURE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ access_modifier class ClassName { โ”‚ โ”œโ”€โ”€ Fields (Instance Variables) โ”€โ”€โ”€โ”€ STATE of the object โ”‚ type variableName; โ”‚ โ”œโ”€โ”€ Constructors โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ INITIALIZATION โ”‚ ClassName(params) { ... } โ”‚ โ”œโ”€โ”€ Methods โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ BEHAVIOR of the object โ”‚ returnType methodName(params) { ... } โ”‚ โ””โ”€โ”€ Nested Classes, Blocks, etc. } โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Class members = Fields + Constructors + Methods + Nested Types

๐Ÿ“ฆ What is an Object?

An object is a concrete, real instance of a class created at runtime. When you use the new keyword, the JVM:

  1. Allocates memory in the Heap for the object's fields
  2. Runs the constructor to initialize the object
  3. Returns a reference (memory address) stored in the Stack variable
OBJECT CREATION โ€” MEMORY LAYOUT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Student s1 = new Student(); STACK HEAP โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ s1 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ Student object โ”‚ โ”‚ [ref: 0x4A]โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ name = null โ”‚โ”‚ โ”‚ โ”‚ age = 0 โ”‚โ”‚ Reference lives โ”‚ โ”‚ marks= 0.0 โ”‚โ”‚ on Stack โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ Object lives on Heap โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Two references, ONE object: Student s2 = s1; โ† s2 also points to same Heap object! s2.name = "Rahul"; โ† s1.name is NOW also "Rahul"

๐Ÿท๏ธ Types of Classes in Java

TypeDescriptionExample
Concrete ClassNormal, fully implemented class. Can be instantiated.class Student { }
Abstract ClassHas abstract keyword. Cannot be instantiated directly. May have abstract methods.abstract class Shape { }
Final ClassCannot be subclassed. Used to prevent inheritance.final class String { }
Inner ClassDefined inside another class.class Outer { class Inner { } }
Anonymous ClassClass with no name, defined inline for one-time use.new Runnable() { ... }
Pre-defined ClassProvided by the Java standard library (JDK).String, Math, ArrayList
User-defined ClassCreated by the programmer for the application.class Employee { }

๐Ÿ”— Accessing Members via Object

Once an object is created, you access its fields and methods using the dot operator (.):

DOT OPERATOR โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ objectReference . memberName s1.name โ† access a field s1.printInfo() โ† call a method s1.age = 20 โ† set a field value โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

๐Ÿ”„ Multiple Objects โ€” Independent State

Each object created from the same class has its own independent copy of instance variables. Changing one object's state does not affect another (unless they share a reference).

โš ๏ธ Reference Copy vs Object Copy
Student s2 = s1; does NOT create a new object. It creates a second reference pointing to the same object in Heap. Any changes via s2 will be visible via s1. To create a truly independent copy, use a Copy Constructor (covered in Topic 10).

๐Ÿ”ง Default Values of Instance Variables

TypeDefault Value
int, long, short, byte0
float, double0.0
booleanfalse
char'\u0000' (null char)
Any object reference (String, arrays, etc.)null
๐Ÿ“Œ Instance vs Local Variables
Instance variables (declared inside class, outside methods) are automatically given default values by the JVM.
Local variables (declared inside a method) are NOT given defaults โ€” the compiler will throw an error if you use them without initializing.

๐Ÿ’ป Program 1 โ€” Basic Class and Object

Student.java
public class Student {

    // Instance variables โ€” STATE
    String name;
    int age;
    double marks;

    // Method โ€” BEHAVIOR
    void printInfo() {
        System.out.println("--- Student Info ---");
        System.out.println("Name  : " + name);
        System.out.println("Age   : " + age);
        System.out.println("Marks : " + marks);
    }

    public static void main(String[] args) {

        // Create object โ€” memory allocated in Heap
        Student s1 = new Student();

        // Assign values via dot operator
        s1.name  = "Rahul";
        s1.age   = 20;
        s1.marks = 88.5;

        // Create second independent object
        Student s2 = new Student();
        s2.name  = "Priya";
        s2.age   = 22;
        s2.marks = 94.0;

        s1.printInfo();
        s2.printInfo();
    }
}
โ–ถ OUTPUT
--- Student Info --- Name : Rahul Age : 20 Marks : 88.5 --- Student Info --- Name : Priya Age : 22 Marks : 94.0

๐Ÿ’ป Program 2 โ€” Default Values Before Assignment

DefaultValues.java
public class DefaultValues {

    int     count;       // default: 0
    double  price;       // default: 0.0
    boolean active;      // default: false
    String  label;       // default: null
    char    grade;       // default: '\u0000'

    void showDefaults() {
        System.out.println("int     : " + count);
        System.out.println("double  : " + price);
        System.out.println("boolean : " + active);
        System.out.println("String  : " + label);
        System.out.println("char    : '" + grade + "' (unicode: " + (int) grade + ")");
    }

    public static void main(String[] args) {
        new DefaultValues().showDefaults();
    }
}
โ–ถ OUTPUT
int : 0 double : 0.0 boolean : false String : null char : '' (unicode: 0)

๐Ÿ’ป Program 3 โ€” Reference Copy Trap

ReferenceTrap.java
public class ReferenceTrap {

    String name;
    int age;

    public static void main(String[] args) {

        ReferenceTrap obj1 = new ReferenceTrap();
        obj1.name = "Arjun";
        obj1.age  = 25;

        // โš ๏ธ This does NOT create a new object!
        // obj2 is just another reference to the SAME Heap object
        ReferenceTrap obj2 = obj1;

        obj2.name = "Changed!";  // modifies the shared object

        // Both reflect the change โ€” same object!
        System.out.println("obj1.name = " + obj1.name);
        System.out.println("obj2.name = " + obj2.name);
        System.out.println("Same object? " + (obj1 == obj2));
    }
}
โ–ถ OUTPUT
obj1.name = Changed! obj2.name = Changed! Same object? true

๐Ÿ’ป Program 4 โ€” Class as a Type (Array of Objects)

ObjectArray.java
public class Car {

    String brand;
    int    year;
    double price;

    void display() {
        System.out.printf("%-10s | %d | โ‚น%.2f%n", brand, year, price);
    }

    public static void main(String[] args) {

        // Array of Car objects โ€” initially all null
        Car[] fleet = new Car[3];

        fleet[0] = new Car();
        fleet[0].brand = "Toyota";  fleet[0].year = 2022;  fleet[0].price = 1500000;
        fleet[1] = new Car();
        fleet[1].brand = "Honda";   fleet[1].year = 2023;  fleet[1].price = 1200000;
        fleet[2] = new Car();
        fleet[2].brand = "Maruti";  fleet[2].year = 2021;  fleet[2].price = 700000;

        System.out.println("Brand      | Year | Price");
        System.out.println("-----------|------|------------");
        for (Car c : fleet) c.display();
    }
}
โ–ถ OUTPUT
Brand | Year | Price -----------|------|------------ Toyota | 2022 | โ‚น1500000.00 Honda | 2023 | โ‚น1200000.00 Maruti | 2021 | โ‚น700000.00
๐Ÿ’ก Best Practice โ€” One Public Class Per File
Java enforces that a file can have only one public class, and the filename must match that class name exactly (case-sensitive). You can have multiple non-public classes in one file, but keep it to one class per file for clean, maintainable code.
โš ๏ธ NullPointerException Trap
If you declare an object variable but forget to instantiate it with new, calling any method on it will throw a NullPointerException at runtime:
Student s; โ† declared but null
s.printInfo(); โ† ๐Ÿ’ฅ NullPointerException!
Always ensure you've used new before calling methods on an object reference.
โญ Must-Know Interview Topics
Class vs Object ยท Heap vs Stack for objects ยท Default values ยท Reference copy trap ยท NullPointerException cause ยท Why filename = class name in Java
Q1. What is the difference between a Class and an Object?
A Class is a compile-time blueprint/template that defines structure and behavior โ€” it takes no runtime memory by itself.

An Object is a runtime instance of a class โ€” allocated on the Heap, with its own copy of instance variables. You create objects using the new keyword.

Analogy: Class = blueprint of a house. Object = the actual house built from that blueprint. You can build many houses from one blueprint, each with different paint colors (state).
Q2. Where are objects stored in Java memory?
Objects are stored in the Heap memory. The reference variable (which holds the address of the object) is stored on the Stack. When a method ends, the stack frame is popped and the reference disappears, but the object in Heap persists until the Garbage Collector reclaims it (when no references point to it).
Q3. What are the default values of instance variables in Java?
Instance variables (fields) in Java are automatically initialized to defaults by the JVM:
int/long/short/byte โ†’ 0
float/double โ†’ 0.0
boolean โ†’ false
char โ†’ '\u0000' (null character)
Object references (String, arrays, custom classes) โ†’ null

Note: local variables are NOT initialized โ€” the compiler throws an error if you use an uninitialized local variable.
Q4. What happens when you do: Animal a2 = a1? (Tricky!)
This is a reference copy, NOT an object copy. Both a1 and a2 now point to the same object in Heap. Modifying the object via a2 will also reflect when accessed via a1. To get a true independent copy, you need a Copy Constructor or implement Cloneable.
Q5. Why must the Java filename match the public class name?
The Java compiler (javac) uses the filename to locate the public class definition. When the JVM needs to load a class, it looks for a .class file with the matching name. If the filename doesn't match the public class name, the compiler throws a compile-time error. This convention also makes it easy for developers and tools to quickly find where a class is defined โ€” you always know UserService.java contains the UserService class.
Q6. Can a Java file have multiple classes? What are the rules?
Yes, a .java file can have multiple class definitions, but with strict rules:
1. Only one class can be public in a file.
2. The filename must exactly match the public class name.
3. Non-public classes in the same file get their own .class file after compilation but can't be accessed from outside the package.

Best practice: one class per file for clarity and maintainability.
Q7. What causes NullPointerException and how do you avoid it?
NullPointerException (NPE) is thrown when you try to call a method or access a field on an object reference that is null (points to no object).

Common causes: forgetting new, a method returning null and not checking, uninitialized array elements.

Prevention: Always initialize references before use, use Optional<T> in modern Java for nullable returns, add null checks before method calls, use Objects.requireNonNull() in constructors.

๐ŸŒฑ Classes & Objects in Spring Boot

In Spring Boot, every component you write is a class. Spring manages the lifecycle of these objects (called Beans) for you โ€” you don't call new manually. Spring's IoC (Inversion of Control) container creates, wires, and destroys objects.

โœ… Spring Bean = Managed Object
A Spring Bean is simply a Java object whose lifecycle is managed by the Spring container. You mark a class with an annotation, and Spring handles new ClassName() for you. This is called Dependency Injection (DI).
Product.java โ€” Spring Boot Entity class
import jakarta.persistence.*;

// @Entity maps this Java class to a database table
@Entity
@Table(name = "products")
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private Double price;
    private Integer stock;

    // Getters and setters (Spring/JPA requires these)
    public Long    getId()    { return id; }
    public String  getName()  { return name; }
    public Double  getPrice() { return price; }
    public Integer getStock() { return stock; }

    public void setName(String name)    { this.name = name; }
    public void setPrice(Double price)  { this.price = price; }
    public void setStock(Integer stock) { this.stock = stock; }
}

Key Spring class-related annotations: @Entity @Component @Service @Repository @RestController @Bean

๐Ÿ“Œ Spring creates objects โ€” you don't
In plain Java you write Student s = new Student();. In Spring Boot you write @Autowired Student s; and Spring injects a managed instance. This is the IoC (Inversion of Control) principle in action โ€” the framework controls object creation, not you.
โญ Must-Know Exam Points
  • A class is a logical entity โ€” takes no memory at runtime
  • An object is a physical entity โ€” allocated in Heap
  • Objects are created using the new keyword
  • Default value of object references is null
  • Default value of int is 0, boolean is false
  • Student s2 = s1 โ†’ reference copy, NOT object copy
  • One public class per .java file; filename must match
  • Dot operator (.) used to access members of an object

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Where is an object stored in Java?HeapStack stores local vars & references, not objects themselves
What is the default value of a String field?nullNot "" โ€” it's null until explicitly assigned
Does a class take memory?NoOnly objects take memory. Class is a template.
Dog d2 = d1; โ€” how many objects?1 objectOnly one object in Heap; two references pointing to it
Can a .java file have 2 public classes?NoCompiler error โ€” only 1 public class allowed per file
Default value of char?'\u0000'Not '0' โ€” it's the null Unicode character (value 0)

๐Ÿ“– One-Liner Definitions

  • Class โ€” A logical blueprint defining the structure and behavior of objects; takes no runtime memory.
  • Object โ€” A runtime instance of a class stored in the Heap with its own state.
  • Instance Variable โ€” A field declared inside a class but outside methods; each object gets its own copy.
  • Reference Variable โ€” A variable that holds the memory address (reference) of an object in Heap.
  • new keyword โ€” Allocates Heap memory and calls the constructor to create an object.
  • Dot Operator (.) โ€” Used to access fields and methods of an object via its reference.
  • NullPointerException โ€” Runtime exception thrown when a null reference is used to access members.
TOPIC 03

Methods in Java

โš™๏ธ What is a Method?

A method is a named block of code inside a class that performs a specific task. Methods define the behavior of an object. Calling a method executes that block of code without rewriting it every time โ€” this is the essence of code reuse.

๐Ÿ“Œ Method vs Function
In Java, there are no standalone functions. Every reusable block of code must live inside a class and is called a method. The term "function" is used in languages like C or Python where code can exist outside a class. In Java: it's always a method.

Anatomy of a Method

METHOD STRUCTURE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ accessModifier returnType methodName ( parameterList ) { โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ type name, type name, ... โ”‚ โ”‚ โ””โ”€โ”€โ”€ must match class/variable naming rules โ”‚ โ””โ”€โ”€โ”€ void (nothing) or any data type โ””โ”€โ”€โ”€ public / private / protected / default method body โ€” statements return value; โ† required if returnType is NOT void } โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Example: public int add ( int a, int b ) { return a + b; }

๐Ÿ” Return Types

Every method must declare what it returns. Java enforces this at compile time:

  • void โ€” method returns nothing. No return statement needed (or bare return;).
  • Primitive type (int, double, boolean, etc.) โ€” method must return that exact type.
  • Object/reference type (String, Student, List<T>, etc.) โ€” method must return that object.
  • Object โ€” can return any object (least specific, avoid if possible).
โš ๏ธ Return type is NOT part of the method signature
The method signature = method name + parameter types. Return type is not included. This is crucial for Method Overloading โ€” you cannot overload two methods that differ only in return type. The compiler cannot distinguish them.

๐Ÿ“จ Parameters vs Arguments

PARAMETERS vs ARGUMENTS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ void greet( String name, int times ) โ† PARAMETERS โ”‚ โ”‚ โ”‚ โ””โ”€ declared in method signature โ””โ”€โ”€โ”€ variable placeholders obj.greet( "Rahul", 3 ) โ† ARGUMENTS โ”‚ โ”‚ โ””โ”€โ”€โ”€ actual values passed at call time โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Parameters = variables in the definition Arguments = actual values in the call

๐Ÿ“ž How Java Passes Arguments โ€” Pass by Value

Java is always pass-by-value. This is one of the most misunderstood concepts in Java:

  • For primitives: a copy of the value is passed. The original is never modified.
  • For objects: a copy of the reference (address) is passed. The method can modify the object's fields via this reference, but cannot reassign the original variable.
PASS BY VALUE โ€” PRIMITIVES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ int x = 10; modify(x); โ† copy of 10 is sent // x is still 10 after the call PASS BY VALUE โ€” OBJECT REFERENCE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Student s = new Student("Rahul"); modify(s); โ† copy of reference (address) is sent โ† method CAN change s.name via the address โ† but cannot make s point to a new object

๐Ÿ”€ Method Overloading

Method Overloading means defining multiple methods in the same class with the same name but different parameter lists. Java resolves which one to call at compile time based on the number, types, and order of arguments โ€” making it compile-time (static) polymorphism.

Three valid ways to overload:

  • Different number of parameters โ€” add(int a, int b) vs add(int a, int b, int c)
  • Different types of parameters โ€” add(int a, int b) vs add(double a, double b)
  • Different order of parameter types โ€” show(int a, String b) vs show(String a, int b)
โš ๏ธ Cannot overload by return type alone
int getValue() and double getValue() in the same class = compile error. The compiler resolves overloading based on the call's argument list. Since return type is not part of the call, it cannot disambiguate. This will always fail.

๐Ÿ”— Method Chaining

When a method returns this (the current object), you can chain multiple calls together. This is widely used in the Builder pattern and StringBuilder:

METHOD CHAINING โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ builder.setName("Rahul") .setAge(20) .setMarks(95.0) .build(); โ†‘ Each method returns 'this', enabling the next call โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

๐Ÿ“ฆ Types of Methods in Java

TypeDescriptionExample
Instance MethodBelongs to an object. Needs object to call. Can access instance variables.obj.getName()
Static MethodBelongs to the class. Called without object. Cannot access instance variables directly.Math.sqrt()
Abstract MethodDeclared without body. Must be overridden by subclass.abstract void draw();
Final MethodCannot be overridden in subclasses.final void lock()
Synchronized MethodThread-safe. Only one thread can execute at a time.synchronized void add()
Native MethodImplemented in another language (C/C++) via JNI.native int compute();

๐Ÿ’ป Program 1 โ€” void Method and Return Type Method

Calculator.java
public class Calculator {

    // void method โ€” performs action, returns nothing
    void printSum(int a, int b) {
        System.out.println("Sum (void): " + (a + b));
    }

    // int return type โ€” returns computed value to caller
    int getSum(int a, int b) {
        return a + b;
    }

    // double return type
    double getAverage(int a, int b, int c) {
        return (a + b + c) / 3.0;
    }

    // boolean return type โ€” useful for validation
    boolean isEven(int n) {
        return n % 2 == 0;
    }

    public static void main(String[] args) {
        Calculator calc = new Calculator();

        calc.printSum(5, 3);

        int result = calc.getSum(5, 3);
        System.out.println("Sum (return): " + result);

        System.out.println("Average: " + calc.getAverage(10, 20, 30));
        System.out.println("Is 8 even? " + calc.isEven(8));
        System.out.println("Is 7 even? " + calc.isEven(7));
    }
}
โ–ถ OUTPUT
Sum (void): 8 Sum (return): 8 Average: 20.0 Is 8 even? true Is 7 even? false

๐Ÿ’ป Program 2 โ€” Method Overloading (all 3 ways)

OverloadDemo.java
public class OverloadDemo {

    // Overload 1 โ€” different NUMBER of parameters
    int add(int a, int b) {
        System.out.print("add(int,int) โ†’ ");
        return a + b;
    }

    int add(int a, int b, int c) {
        System.out.print("add(int,int,int) โ†’ ");
        return a + b + c;
    }

    // Overload 2 โ€” different TYPES of parameters
    double add(double a, double b) {
        System.out.print("add(double,double) โ†’ ");
        return a + b;
    }

    // Overload 3 โ€” different ORDER of parameter types
    String display(int id, String name) {
        System.out.print("display(int,String) โ†’ ");
        return id + ": " + name;
    }

    String display(String name, int id) {
        System.out.print("display(String,int) โ†’ ");
        return name + " (ID: " + id + ")";
    }

    public static void main(String[] args) {
        OverloadDemo obj = new OverloadDemo();

        System.out.println(obj.add(2, 3));
        System.out.println(obj.add(2, 3, 4));
        System.out.println(obj.add(2.5, 3.5));
        System.out.println(obj.display(101, "Rahul"));
        System.out.println(obj.display("Priya", 102));
    }
}
โ–ถ OUTPUT
add(int,int) โ†’ 5 add(int,int,int) โ†’ 9 add(double,double) โ†’ 6.0 display(int,String) โ†’ 101: Rahul display(String,int) โ†’ Priya (ID: 102)

๐Ÿ’ป Program 3 โ€” Pass by Value: Primitive vs Object

PassByValue.java
class Box {
    int value;
    Box(int v) { value = v; }
}

public class PassByValue {

    // Primitive โ€” original NOT modified
    static void tryDoubleInt(int x) {
        x = x * 2;  // only modifies local copy
    }

    // Object โ€” field CAN be modified via reference copy
    static void tryDoubleBox(Box b) {
        b.value = b.value * 2;  // modifies actual Heap object
    }

    // Object reassignment โ€” does NOT affect original reference
    static void tryReplaceBox(Box b) {
        b = new Box(999);  // only changes local copy of reference
    }

    public static void main(String[] args) {
        int num = 10;
        tryDoubleInt(num);
        System.out.println("After tryDoubleInt: " + num);  // still 10

        Box box = new Box(10);
        tryDoubleBox(box);
        System.out.println("After tryDoubleBox: " + box.value);  // 20 โ€” changed!

        Box box2 = new Box(10);
        tryReplaceBox(box2);
        System.out.println("After tryReplaceBox: " + box2.value);  // still 10
    }
}
โ–ถ OUTPUT
After tryDoubleInt: 10 After tryDoubleBox: 20 After tryReplaceBox: 10

๐Ÿ’ป Program 4 โ€” Method Chaining with Builder Pattern

PersonBuilder.java
public class PersonBuilder {

    String name;
    int age;
    String city;

    // Each setter returns 'this' โ†’ enables chaining
    PersonBuilder setName(String name) {
        this.name = name;
        return this;
    }

    PersonBuilder setAge(int age) {
        this.age = age;
        return this;
    }

    PersonBuilder setCity(String city) {
        this.city = city;
        return this;
    }

    void print() {
        System.out.println(name + " | Age: " + age + " | City: " + city);
    }

    public static void main(String[] args) {
        // Fluent method chaining
        new PersonBuilder()
            .setName("Rahul")
            .setAge(25)
            .setCity("Mumbai")
            .print();
    }
}
โ–ถ OUTPUT
Rahul | Age: 25 | City: Mumbai
๐Ÿ’ก Best Practice โ€” Keep Methods Small and Focused
A method should do one thing and do it well โ€” this is the Single Responsibility Principle. If a method is longer than ~20 lines or has an "and" in its name (e.g., validateAndSave()), it's a sign it should be split into two separate methods.
โญ Must-Know Interview Topics
Method signature definition ยท Why you can't overload on return type ยท Pass by value vs pass by reference ยท Method vs function in Java ยท Compile-time vs runtime polymorphism
Q1. What is a method signature in Java?
A method signature consists of the method name + parameter types (in order). It does NOT include the return type, access modifier, or parameter names.

Example: for public int add(int a, double b), the signature is add(int, double).

The signature is what the compiler uses to resolve method calls โ€” especially for overloading.
Q2. What is Method Overloading? What are the rules?
Method Overloading = multiple methods with the same name but different parameter lists in the same class.

Valid overloading requires one of: different number of parameters, different types of parameters, or different order of parameter types.

Invalid: changing only the return type โ€” the compiler can't tell which version to call since the return type is not visible at the call site.

Overloading is resolved at compile time โ†’ it is compile-time / static polymorphism.
Q3. Is Java pass-by-value or pass-by-reference? (Very common!)
Java is always pass-by-value โ€” no exceptions.

For primitives: the actual value is copied into the method parameter. Changes inside the method don't affect the original.

For object references: the value of the reference (i.e., the memory address) is copied. The method gets a copy of the pointer. It can modify the object's fields via that pointer, but it cannot make the original variable point to a different object.

This is why people get confused โ€” modifying an object's field inside a method does persist, but reassigning the reference does not.
Q4. What is the difference between a method and a function?
In Java: there are no standalone functions. Every named reusable code block must be inside a class, making it a method. Methods are associated with an object (instance methods) or a class (static methods).

A function is a standalone block of code not tied to any class (common in C, Python, JavaScript). Java doesn't support this at the language level โ€” though lambda expressions in Java 8+ bring functional-style programming, they are still backed by functional interfaces (which are classes).
Q5. Can we overload the main() method in Java?
Yes! You can overload main() by giving it different parameter lists. The JVM will always call public static void main(String[] args) as the entry point โ€” the overloaded versions are just regular methods that you'd have to call manually. Overloading main() is valid Java but rarely useful in practice.
Q6. What is the difference between compile-time and runtime polymorphism?
Compile-time polymorphism (static binding): resolved by the compiler before the program runs. Achieved through Method Overloading. The compiler picks the right method based on argument types at compile time.

Runtime polymorphism (dynamic binding): resolved by the JVM while the program is running. Achieved through Method Overriding. The JVM picks the right method based on the actual object type at runtime, even if the reference type is a parent class.
Q7. Tricky: What happens when you call add(1, 2) and there are both add(int, int) and add(long, long)?
The compiler applies widening type promotion. int literals 1 and 2 exactly match add(int, int), so that version is called. If only add(long, long) existed, Java would automatically widen the int arguments to long and call it. Java's overload resolution order is: exact match โ†’ widening โ†’ autoboxing โ†’ varargs.

๐ŸŒฑ Methods in Spring Boot

In Spring Boot, methods are at the core of every layer โ€” controllers handle HTTP requests, services contain business logic, and repositories define data access. Method design directly impacts your API's clarity and maintainability.

โœ… Method Overloading in Spring Services
Use overloaded service methods to handle different search/filter scenarios without creating confusingly named methods like findUserByIdOrEmail().
OrderService.java โ€” Spring Boot
@Service
public class OrderService {

    private final OrderRepository repo;

    public OrderService(OrderRepository repo) {
        this.repo = repo;
    }

    // Overloaded methods โ€” same intent, different filters
    public List<Order> findOrders() {
        return repo.findAll();
    }

    public List<Order> findOrders(String status) {
        return repo.findByStatus(status);
    }

    public List<Order> findOrders(String status, Long userId) {
        return repo.findByStatusAndUserId(status, userId);
    }

    // void method โ€” side effect only (send email, log, etc.)
    public void cancelOrder(Long orderId) {
        Order order = repo.findById(orderId).orElseThrow();
        order.setStatus("CANCELLED");
        repo.save(order);
    }
}
๐Ÿ“Œ Naming Convention in Spring Boot Methods
Follow these patterns: getX() / findX() for reads, createX() / saveX() for inserts, updateX() for modifications, deleteX() / removeX() for deletes. Spring Data JPA also uses method names to auto-generate queries โ€” findByEmailAndActive(String email, boolean active) generates the SQL automatically.
โญ Must-Know Exam Points
  • Method signature = name + parameter types (return type NOT included)
  • Method overloading = same name, different parameter list = compile-time polymorphism
  • Cannot overload by return type alone โ€” compile error
  • Java is always pass-by-value โ€” even for objects (reference copy, not object copy)
  • void methods do not return any value
  • In Java there are no standalone functions โ€” only methods inside classes
  • Overloading resolution order: exact match โ†’ widening โ†’ autoboxing โ†’ varargs

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Can two methods differ only in return type?No โ€” compile errorReturn type is not part of the signature. Compiler can't distinguish.
Java is pass-by-__?ValueEven for objects โ€” the reference value is copied, not the object itself
Method overloading is resolved at __?Compile timeRuntime resolution = overriding. Compile-time = overloading.
Can main() be overloaded?YesValid, but JVM only calls main(String[]) as entry point
What is the return type of a constructor?None (not even void)Constructors have no return type โ€” not even void. This is a defining rule.
Which type of method cannot access instance variables?Static methodStatic methods belong to the class, not to any object, so no this

๐Ÿ“– One-Liner Definitions

  • Method โ€” A named block of code inside a class that defines the behavior of an object.
  • Method Signature โ€” Method name + parameter types. Used by compiler to uniquely identify a method.
  • Method Overloading โ€” Multiple methods with the same name but different parameter lists in one class.
  • void โ€” Return type keyword meaning the method returns no value.
  • Pass by Value โ€” Java always passes a copy of the value (primitive value or reference address) to methods.
  • Static Method โ€” A method belonging to the class, callable without creating an object.
  • Method Chaining โ€” Calling multiple methods on the same object in a single expression by returning this.
TOPIC 04

Memory Management โ€” Stack & Heap

๐Ÿง  Why Does Memory Management Matter?

Every variable, object, and method call in a Java program occupies memory at runtime. The JVM divides this memory into distinct regions, each with a specific purpose. The two most important regions for a Java developer are the Stack and the Heap.

๐Ÿ“Œ JVM Memory Areas โ€” Full Picture
The JVM manages: Stack (method frames & local vars), Heap (objects & instance vars), Metaspace (class definitions, static vars, bytecode โ€” replaces PermGen since Java 8), PC Register (current instruction per thread), and Native Method Stack (for C/C++ calls). For OOP, Stack and Heap matter most.

๐Ÿ“š The Stack

The Stack is a Last-In-First-Out (LIFO) memory region. Every method call pushes a new stack frame; when the method returns, that frame is popped and all its data is gone.

Each stack frame holds: local variables, reference variables, method parameters, and the return address.

๐Ÿ“Œ Stack is per-thread
Every thread gets its own Stack โ€” local variables are thread-safe by default. The Heap is shared across all threads, which is why concurrent object access requires synchronization.

๐Ÿ—๏ธ The Heap

The Heap is a large shared pool where all objects and instance variables live. Every new ClassName() allocates Heap memory. The Garbage Collector automatically reclaims memory from unreachable objects.

๐ŸŽจ Full Memory Diagram

JVM MEMORY โ€” STACK vs HEAP โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• Code: Student s1 = new Student("Rahul", 20); Student s2 = new Student("Priya", 22); Student s3 = s1; โ† reference copy, NO new object STACK (main thread) HEAP โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ main() frame โ”‚ โ”‚ 0x4A1F โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ s1 โ†’ 0x4A1F โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ name = "Rahul" โ”‚ โ”‚ โ”‚ s2 โ†’ 0x7C32 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ age = 20 โ”‚ โ”‚ โ”‚ s3 โ†’ 0x4A1F โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ 0x7C32 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ name = "Priya" โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ age = 22 โ”‚ โ”‚ s1 and s3 โ†’ SAME object! โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• Stack: LIFO, per-thread, auto-cleaned when frame pops Heap: shared, GC-managed, lives until zero references remain

๐Ÿ”„ Stack Frame Lifecycle

STACK FRAME LIFECYCLE โ€” main() calls greet() โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ BEFORE call: DURING greet(): AFTER return: โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ main() frameโ”‚ โ”‚ greet() โ†TOPโ”‚ โ”‚ main() frameโ”‚ โ”‚ x = 5 โ”‚ โ”‚ msg="Hi" โ”‚ โ”‚ x = 5 โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ count=3 โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค greet() frame GONE โ”‚ main() frameโ”‚ greet()'s vars GONE โ”‚ x = 5 โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Objects greet() referenced in Heap may still exist if other references point to them.

โ™ป๏ธ Garbage Collection

When no reference points to a Heap object, it becomes unreachable and is eligible for GC. You can suggest GC with System.gc() but the JVM may ignore it. Never rely on GC timing โ€” use try-with-resources for IO/DB cleanup.

GC ELIGIBILITY โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Student s = new Student("Rahul"); s = new Student("Priya"); โ† "Rahul" obj โ†’ 0 refs โ†’ GC eligible s = null; โ† "Priya" obj โ†’ 0 refs โ†’ GC eligible

๐Ÿ’ฅ Common Memory Errors

ErrorCauseArea
StackOverflowErrorInfinite recursion โ€” frames pile up until Stack exhaustedStack
OutOfMemoryError: Java heap spaceToo many objects, none becoming unreachable โ€” memory leakHeap
NullPointerExceptionDereferencing a null reference โ€” points to no objectStack (null ref)
OutOfMemoryError: MetaspaceToo many classes loaded dynamicallyMetaspace

๐Ÿ—บ๏ธ Where Does Each Variable Live?

Variable TypeMemory LocationExample
Local primitiveStackint x = 5; inside a method
Local reference variableStack (address only)Student s = ... โ€” s itself on Stack
Object itselfHeapThe actual Student object fields
Instance variable (field)Heap (inside object)String name; in class body
Static variableMetaspacestatic int count;
String literalString Pool (Heap)String s = "hello";
๐Ÿ“Œ String Pool Deep Dive
Java maintains a String Pool inside the Heap. String a = "hello" checks the pool first โ€” if "hello" exists, reuses it. That's why "hello" == "hello" is true. But new String("hello") == new String("hello") is false โ€” new always bypasses the pool. Use .intern() to force a string into the pool.

๐Ÿ’ป Program 1 โ€” Stack vs Heap: References & GC Eligibility

MemoryDemo.java
class Student {
    String name;   // instance var โ†’ HEAP (inside object)
    int    age;    // instance var โ†’ HEAP (inside object)
    Student(String name, int age) { this.name=name; this.age=age; }
}

public class MemoryDemo {

    static void printStudent(Student s) {
        // 's' = local reference on THIS frame โ†’ points to same Heap object
        int localVar = 42;  // on THIS stack frame only, gone after return
        System.out.println(s.name + " | Age: " + s.age);
    }

    public static void main(String[] args) {
        Student s1 = new Student("Rahul", 20);  // s1 ref on Stack, object on Heap
        Student s2 = new Student("Priya", 22);  // s2 ref on Stack, new object on Heap
        Student s3 = s1;                     // s3 ref on Stack, SAME Heap object as s1

        printStudent(s1);
        printStudent(s2);

        s3.name = "Changed via s3";
        System.out.println("s1.name = " + s1.name);  // reflects s3's change!

        s1 = null;  // object still alive โ€” s3 still points to it
        System.out.println("s3.name = " + s3.name);
        s3 = null;  // NOW โ†’ 0 references โ†’ GC eligible
    }
}
โ–ถ OUTPUT
Rahul | Age: 20 Priya | Age: 22 s1.name = Changed via s3 s3.name = Changed via s3

๐Ÿ’ป Program 2 โ€” StackOverflowError via Infinite Recursion

StackOverflowDemo.java
public class StackOverflowDemo {

    static void recurse(int n) {
        System.out.println("Call #" + n);
        recurse(n + 1);  // no base case โ†’ infinite stack frames
    }

    public static void main(String[] args) {
        try {
            recurse(1);
        } catch (StackOverflowError e) {
            System.out.println("๐Ÿ’ฅ StackOverflowError! Stack memory exhausted.");
        }
    }
}
โ–ถ OUTPUT
Call #1 Call #2 ... (thousands of calls) ๐Ÿ’ฅ StackOverflowError! Stack memory exhausted.

๐Ÿ’ป Program 3 โ€” String Pool: == vs .equals()

StringPoolDemo.java
public class StringPoolDemo {
    public static void main(String[] args) {

        String a = "hello";             // goes to String Pool
        String b = "hello";             // reuses same pooled object
        String c = new String("hello"); // new Heap object, bypasses pool
        String d = new String("hello"); // another new Heap object

        // == compares REFERENCES (addresses)
        System.out.println("a == b        : " + (a == b));        // true  โ€” same pool obj
        System.out.println("a == c        : " + (a == c));        // false โ€” different objects
        System.out.println("c == d        : " + (c == d));        // false โ€” two new objects

        // .equals() compares CONTENT โ€” use this always for Strings!
        System.out.println("a.equals(c)   : " + a.equals(c));   // true  โ€” same content

        // intern() forces into the pool
        String e = c.intern();
        System.out.println("a == e interned: " + (a == e));  // true
    }
}
โ–ถ OUTPUT
a == b : true a == c : false c == d : false a.equals(c) : true a == e interned: true
โš ๏ธ Always use .equals() to compare String content
== on Strings compares memory addresses, not content. It may accidentally work for literals (same pool object) but will fail for new String(), user input, or database values. Best practice: "literal".equals(variable) โ€” literal first to avoid NPE if variable is null.
๐Ÿ’ก Tip โ€” How to tune JVM Heap size
For Spring Boot or any large Java app: java -Xms256m -Xmx1g -jar app.jar
-Xms = initial heap size  |  -Xmx = max heap size. Start with these flags and profile with tools like VisualVM or Java Flight Recorder if you see OutOfMemoryError.
โญ Must-Know Interview Topics
Stack vs Heap ยท Where each variable lives ยท GC basics ยท StackOverflowError vs OutOfMemoryError ยท String Pool ยท == vs .equals() ยท Static vars in Metaspace
Q1. What is the difference between Stack and Heap memory?
Stack: LIFO, per-thread, stores method frames + local vars + references. Auto-freed on method return. Fast. Fixed size โ†’ StackOverflowError if full.

Heap: Shared across threads, stores all objects + instance vars. GC-managed. Slower allocation. Configurable size โ†’ OutOfMemoryError if full.
Q2. Where are local vs instance variables stored?
Local variables (inside methods): on the Stack, in the current frame. Gone when method returns.

Instance variables (class fields): on the Heap, inside the object. Alive as long as the object is reachable.

Note: a local reference variable is on the Stack, but the object it points to is on the Heap.
Q3. What is Garbage Collection? How does it work?
GC automatically reclaims Heap memory from unreachable objects (no active reference points to them). Modern JVMs use Generational GC: Young Generation (new objects, Minor GC runs frequently) and Old/Tenured Generation (long-lived objects, Major/Full GC runs rarely).

System.gc() suggests GC but is not guaranteed. Never rely on GC timing for resource cleanup โ€” use try-with-resources instead.
Q4. What causes StackOverflowError vs OutOfMemoryError?
StackOverflowError: Stack exhausted โ€” almost always caused by infinite recursion (no base case). Each call pushes a frame until no space remains.

OutOfMemoryError: Java heap space: Heap exhausted โ€” caused by creating objects faster than GC can collect them, or by memory leaks (objects kept alive in collections/listeners never cleaned up).
Q5. What is the String Pool and why does it exist?
The String Pool (intern pool) is a cache inside the Heap for String literals. When you write "hello", Java checks if it's in the pool โ€” if yes, reuses that object; if no, creates and adds it. This saves memory because Strings are the most-used objects and many have identical values.

new String("hello") bypasses the pool and creates a fresh Heap object. .intern() manually adds a String to the pool.
Q6. Why use .equals() instead of == for Strings?
== checks if two references point to the same object in memory (same address).
.equals() checks if two Strings have the same content.

For literals, == may accidentally return true (same pooled object). But for new String(), user input, or values from a database, identical content may live in different objects โ€” == would return false. Always use .equals() for content comparison. Use "literal".equals(var) to prevent NPE.
Q7. Where are static variables stored?
Static variables are stored in Metaspace (called the Method Area). Since Java 8, Metaspace replaced PermGen and uses native memory outside the regular Heap. It stores class metadata, bytecode, static variables, and static methods. Metaspace grows dynamically by default and has its own OutOfMemoryError: Metaspace error when exhausted.

๐ŸŒฑ Memory Management in Spring Boot

Spring Boot runs inside a JVM โ€” all Stack/Heap rules apply. Bean scope directly controls how many objects Spring creates in the Heap.

โœ… Bean Scope and Heap Usage
Singleton (default): ONE object in Heap for the entire app โ€” memory efficient, used for stateless services.
Prototype: NEW Heap object every injection request โ€” use sparingly, watch memory.
Request: New object per HTTP request, GC-eligible after the request ends.
BeanScopeDemo.java โ€” Spring Boot
// SINGLETON โ€” one Heap object for entire app lifetime (default)
@Service
public class UserService { }

// PROTOTYPE โ€” new Heap object every time it's requested
@Component
@Scope("prototype")
public class ReportGenerator { }

// JVM Heap tuning flags for Spring Boot production:
// java -Xms256m -Xmx1g -jar myapp.jar
//  -Xms = initial heap size (avoid expensive resizing at startup)
//  -Xmx = max heap size (cap it based on your server's RAM)
โš ๏ธ Common Spring Memory Leak โ€” Unclosed Resources
Failing to close DB connections, HTTP clients, or file streams leaves objects referenced in connection pools โ€” they never become unreachable, so GC cannot collect them. The Heap gradually fills up. Always use try-with-resources or rely on Spring-managed beans which handle lifecycle cleanup automatically.
โญ Must-Know Exam Points
  • Stack = LIFO, per-thread, holds local vars & frames, auto-cleaned
  • Heap = shared, holds all objects, GC-managed
  • Instance variables โ†’ Heap (inside object)
  • Local variables โ†’ Stack (including reference vars)
  • Static variables โ†’ Metaspace (Method Area)
  • StackOverflowError โ†’ infinite recursion โ†’ Stack full
  • OutOfMemoryError โ†’ too many objects โ†’ Heap full
  • String literals โ†’ String Pool (inside Heap) โ€” shared/reused
  • new String("x") โ†’ always new Heap object, bypasses pool
  • Always use .equals() for String content comparison, never ==

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Where do objects live in Java?HeapStack only holds references and primitives, not objects themselves
Where do instance variables live?Heap (inside object)They travel with the object โ€” not on any method's stack frame
Where do static variables live?Metaspace / Method AreaNot Stack, not Heap โ€” separate class-level memory
String a="x"; String b="x"; a==b?trueBoth reference the same pooled String object
new String("x") == new String("x")?falsenew always creates fresh Heap objects, bypasses pool
Infinite recursion causes what error?StackOverflowErrorNot OutOfMemoryError โ€” specifically the Stack overflows
Is Stack shared between threads?NoEach thread has its own Stack. Heap IS shared.

๐Ÿ“– One-Liner Definitions

  • Stack โ€” LIFO per-thread memory holding method frames, local variables, and references.
  • Heap โ€” Shared memory where all Java objects and instance variables live.
  • Stack Frame โ€” A block of Stack memory for a single method call; destroyed on return.
  • Garbage Collection โ€” Automatic JVM process reclaiming Heap memory from unreachable objects.
  • String Pool โ€” A cache of String literals inside the Heap enabling reuse of identical strings.
  • Metaspace โ€” Native memory (Java 8+) storing class metadata and static variables.
  • StackOverflowError โ€” Error thrown when Stack memory is exhausted, typically via infinite recursion.
TOPIC 05

Arrays (Primitives & Objects)

๐Ÿ“ฆ What is an Array?

An array is a fixed-size, ordered collection of elements of the same data type, stored in contiguous memory locations in the Heap. Arrays are the simplest data structure in Java and form the foundation of many other collections like ArrayList.

๐Ÿ“Œ Key Properties of Arrays
  • Fixed size โ€” once created, the length cannot change
  • Zero-indexed โ€” first element is at index 0, last at length - 1
  • Same type โ€” all elements must be the same data type
  • Heap-allocated โ€” arrays are objects in Java, stored in Heap
  • Default values โ€” elements initialized to type defaults (0, false, null)

๐Ÿ”ง Declaring and Creating Arrays

There are three ways to create an array in Java:

ARRAY CREATION โ€” THREE STYLES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. Declare + allocate separately: int[] marks; // declaration โ€” null reference on Stack marks = new int[5]; // allocation โ€” array object on Heap 2. Declare + allocate together: int[] marks = new int[5]; // 5 elements, all initialized to 0 3. Declare + allocate + initialize (array literal): int[] marks = {85, 90, 78, 92, 88}; // size inferred = 5 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ marks[0] = 85 marks[1] = 90 marks[2] = 78 ... โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ” โ”‚ 85 โ”‚ 90 โ”‚ 78 โ”‚ 92 โ”‚ 88 โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”˜ [0] [1] [2] [3] [4] marks.length = 5 Valid indices: 0 to 4 Accessing marks[5] โ†’ ArrayIndexOutOfBoundsException!

๐Ÿง  Arrays in Memory

ARRAY MEMORY LAYOUT โ€” STACK AND HEAP โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ int[] marks = new int[3]; STACK HEAP โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ marks โ†’ 0xA1 โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ int[3] object at 0xA1 โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ [0] = 0 โ”‚ โ”‚ [1] = 0 โ”‚ โ”‚ [2] = 0 โ”‚ โ”‚ .length = 3 โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ Student[] students = new Student[2]; โ† array of references! HEAP: int[2] object โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ [0]=null โ”‚ [1]=null โ”‚ โ† each slot holds a reference โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ (not yet (not yet assigned) assigned) After: students[0] = new Student("Rahul"); โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚[0]โ†’0xB2 โ”‚[1]=null โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ–ผ Student object at 0xB2 {name="Rahul", age=20}
โš ๏ธ Array of Objects โ‰  Array of values
When you create Student[] students = new Student[3], you get an array of 3 null references โ€” not 3 Student objects. You must separately create each object with new Student() and assign it to each slot. Forgetting this causes NullPointerException when accessing elements.

๐Ÿ” Iterating Arrays

Java provides two ways to loop through an array:

FOR LOOP vs FOR-EACH LOOP โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ int[] nums = {10, 20, 30, 40, 50}; // Standard for loop โ€” use when you need the INDEX for (int i = 0; i < nums.length; i++) { System.out.println(nums[i]); } // Enhanced for-each loop โ€” use when you only need the VALUE for (int n : nums) { System.out.println(n); } โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ For-each CANNOT: โœ— modify the array (changes to n don't affect the array) โœ— access the index โœ— iterate in reverse Use standard for loop when you need: index, reverse, modify

๐Ÿ“ Multi-Dimensional Arrays

Java supports arrays of arrays โ€” commonly used for matrices and grids:

2D ARRAY โ€” MATRIX โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ int[][] matrix = { {1, 2, 3}, // row 0 {4, 5, 6}, // row 1 {7, 8, 9} // row 2 }; matrix[1][2] = 6 (row 1, col 2) matrix.length = 3 (number of rows) matrix[0].length = 3 (number of cols in row 0) In Memory: array of arrays โ€” each row is a separate Heap object matrix โ†’ [ref0, ref1, ref2] โ”‚ โ”‚ โ”‚ [1,2,3][4,5,6][7,8,9]

๐Ÿ› ๏ธ Useful Array Utilities โ€” java.util.Arrays

MethodPurposeExample
Arrays.sort(arr)Sort in ascending orderArrays.sort(marks)
Arrays.toString(arr)Print array as string"[85, 90, 78]"
Arrays.fill(arr, val)Fill all elements with valueArrays.fill(arr, 0)
Arrays.copyOf(arr, len)Copy with new lengthArrays.copyOf(arr, 3)
Arrays.equals(a, b)Compare two arrays by contentArrays.equals(a, b)
Arrays.binarySearch(arr, key)Search sorted arrayReturns index or negative

๐Ÿ’ป Program 1 โ€” Primitive Array: Declare, Fill, Iterate

PrimitiveArray.java
import java.util.Arrays;

public class PrimitiveArray {
    public static void main(String[] args) {

        // Create and initialise
        int[] marks = {85, 92, 78, 96, 88};

        // length property โ€” NOT a method, no parentheses
        System.out.println("Length: " + marks.length);

        // Standard for loop โ€” with index
        System.out.print("Marks: ");
        for (int i = 0; i < marks.length; i++)
            System.out.print(marks[i] + " ");
        System.out.println();

        // Enhanced for-each โ€” cleaner when index not needed
        int sum = 0;
        for (int m : marks) sum += m;
        System.out.println("Sum: " + sum);
        System.out.println("Average: " + (double) sum / marks.length);

        // Sort and print
        Arrays.sort(marks);
        System.out.println("Sorted: " + Arrays.toString(marks));
    }
}
โ–ถ OUTPUT
Length: 5 Marks: 85 92 78 96 88 Sum: 439 Average: 87.8 Sorted: [78, 85, 88, 92, 96]

๐Ÿ’ป Program 2 โ€” Array of Objects (Student)

ObjectArray.java
class Student {
    String name;
    int    marks;
    Student(String name, int marks) {
        this.name  = name;
        this.marks = marks;
    }
}

public class ObjectArray {
    public static void main(String[] args) {

        // Step 1: create array of 3 null references
        Student[] batch = new Student[3];

        // Step 2: create each object and assign to its slot
        batch[0] = new Student("Rahul", 88);
        batch[1] = new Student("Priya", 94);
        batch[2] = new Student("Arjun", 79);

        // Iterate and print
        System.out.println("Student Results:");
        for (Student s : batch)
            System.out.printf("  %-10s โ†’ %d%n", s.name, s.marks);

        // Find topper
        Student topper = batch[0];
        for (Student s : batch)
            if (s.marks > topper.marks) topper = s;
        System.out.println("Topper: " + topper.name + " (" + topper.marks + ")");
    }
}
โ–ถ OUTPUT
Student Results: Rahul โ†’ 88 Priya โ†’ 94 Arjun โ†’ 79 Topper: Priya (94)

๐Ÿ’ป Program 3 โ€” ArrayIndexOutOfBoundsException Demo

ArrayBoundsDemo.java
public class ArrayBoundsDemo {
    public static void main(String[] args) {
        int[] nums = {10, 20, 30};  // valid indices: 0, 1, 2

        // Safe access
        System.out.println(nums[2]);  // 30 โ€” OK

        // Dangerous โ€” catching the exception
        try {
            System.out.println(nums[5]);  // index 5 doesn't exist!
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("๐Ÿ’ฅ Caught: " + e.getMessage());
        }

        // Common off-by-one mistake โ€” use < not <=
        for (int i = 0; i < nums.length; i++)  // correct: i < length
            System.out.print(nums[i] + " ");
    }
}
โ–ถ OUTPUT
30 ๐Ÿ’ฅ Caught: Index 5 out of bounds for length 3 10 20 30

๐Ÿ’ป Program 4 โ€” 2D Array: Matrix Operations

MatrixDemo.java
public class MatrixDemo {
    public static void main(String[] args) {

        int[][] matrix = {
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}
        };

        // Print as grid
        System.out.println("Matrix:");
        for (int[] row : matrix) {
            for (int val : row)
                System.out.printf("%3d", val);
            System.out.println();
        }

        // Sum of diagonal
        int diag = 0;
        for (int i = 0; i < matrix.length; i++)
            diag += matrix[i][i];
        System.out.println("Diagonal sum: " + diag);
    }
}
โ–ถ OUTPUT
Matrix: 1 2 3 4 5 6 7 8 9 Diagonal sum: 15
โš ๏ธ Common Mistake โ€” printing array with System.out.println
System.out.println(marks) prints something like [I@6d06d69c โ€” the type code and hash, NOT the values. Always use Arrays.toString(marks) for 1D arrays and Arrays.deepToString(matrix) for 2D arrays.
๐Ÿ’ก Tip โ€” Arrays vs ArrayList
Use a plain array when the size is fixed and performance matters (no overhead). Use ArrayList<T> when the size is dynamic, you need to add/remove elements, or you want built-in utility methods. In Spring Boot, you will almost always use List<T> and ArrayList.
โญ Must-Know Interview Topics
Array properties ยท Arrays in Heap ยท Array vs ArrayList ยท for vs for-each limitations ยท ArrayIndexOutOfBoundsException ยท Arrays.sort and Arrays.toString ยท 2D array memory layout
Q1. What is an array in Java? What are its key properties?
An array is a fixed-size, contiguous, same-type collection of elements stored in the Heap. Key properties: zero-indexed, length is fixed at creation, elements auto-initialized to defaults, and the array itself is an object in Java (so it has a .length field and inherits from Object).
Q2. What is the difference between array length and String length()?
array.length is a field (no parentheses) โ€” it's a property stored directly in the array object.

string.length() is a method (with parentheses) โ€” it computes and returns the character count.

This is a classic exam trap. Arrays use .length (field). Strings use .length() (method). Collections like ArrayList use .size() (method).
Q3. What is ArrayIndexOutOfBoundsException? When does it occur?
It's a RuntimeException thrown when you try to access an array element using an index that is either negative or โ‰ฅ array.length. Valid indices are always 0 to length - 1.

Most common cause: the classic off-by-one error using i <= arr.length instead of i < arr.length in a for loop.
Q4. What are the limitations of the enhanced for-each loop on arrays?
The for-each loop cannot:
1. Modify the array โ€” changes to the loop variable don't affect the array
2. Access the index โ€” no counter available
3. Iterate in reverse โ€” always forward only
4. Iterate two arrays simultaneously

Use the standard for loop whenever you need any of the above.
Q5. What is the difference between Array and ArrayList?
ArrayArrayList
Fixed sizeDynamic size โ€” grows/shrinks
Can hold primitivesHolds only objects (uses autoboxing)
Faster โ€” no overheadSlightly slower โ€” resizing overhead
arr.lengthlist.size()
arr[i] for accesslist.get(i) for access
No built-in add/removeBuilt-in add(), remove()
Q6. How are arrays stored in memory? Are they on Stack or Heap?
Arrays are objects in Java โ€” the array object itself lives on the Heap. The reference variable pointing to the array lives on the Stack.

For an array of objects (e.g., Student[]), the array object on the Heap contains references โ€” each slot holds a reference pointing to its respective Student object elsewhere in the Heap. The array of references and the objects it points to are all in the Heap.
Q7. Tricky: What does new Student[3] actually create?
It creates an array of 3 null references โ€” not 3 Student objects. The Heap contains one array object with 3 slots, each initialized to null. You must separately call new Student(...) for each slot and assign it. Accessing any element before assignment throws NullPointerException.

๐ŸŒฑ Arrays in Spring Boot

In Spring Boot you rarely use raw arrays โ€” List<T> and Collection<T> are preferred because they work natively with JPA, Jackson (JSON), and Spring's dependency injection. But arrays do appear in specific contexts:

โœ… When arrays appear in Spring Boot
@Value injection โ€” inject a comma-separated config value into a String array.
REST API responses โ€” Jackson serializes both arrays and Lists to JSON arrays.
@RequestParam โ€” multiple values for same param come as a String array.
Batch processing โ€” fixed-size byte[] buffers for file/stream processing.
ArrayInSpring.java โ€” Spring Boot
@RestController
public class ProductController {

    // Inject comma-separated config into a String array
    @Value("${app.allowed-categories}")   // application.properties:
    private String[] allowedCategories;   // app.allowed-categories=books,food,electronics

    // Multiple query params โ†’ String array
    @GetMapping("/products")
    public List<Product> getProducts(
            @RequestParam(required = false) String[] tags) {
        // GET /products?tags=java&tags=spring  โ†’  tags = {"java","spring"}
        if (tags == null) return productService.findAll();
        return productService.findByTags(Arrays.asList(tags));
    }
}
๐Ÿ“Œ Arrays.asList() โ€” Bridge between Array and List
Arrays.asList(arr) wraps an array in a fixed-size List view โ€” useful when a Spring method expects a List but you have an array. Note: the returned list is fixed-size โ€” you cannot add() or remove() from it. Use new ArrayList<>(Arrays.asList(arr)) for a fully mutable list.
โญ Must-Know Exam Points
  • Arrays are objects in Java โ€” stored in Heap
  • Arrays are zero-indexed โ€” valid range: 0 to length - 1
  • Array size is fixed after creation โ€” cannot resize
  • Default values: int[] โ†’ 0, boolean[] โ†’ false, Object[] โ†’ null
  • array.length is a field, not a method (no parentheses)
  • new Student[3] creates 3 null references, NOT 3 objects
  • For-each cannot modify array or access index
  • Use Arrays.toString() to print array contents โ€” not println(arr)
  • Accessing invalid index โ†’ ArrayIndexOutOfBoundsException (runtime)

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
How do you get array size?arr.lengthIt's a field, not arr.length() โ€” no parentheses
First element of array is at index?0Zero-indexed โ€” a common off-by-one mistake
new int[5] โ€” what are the values?All 0Default init โ€” not garbage values like C/C++
new Student[3] โ€” how many Student objects?0Creates 3 null references, no Student objects yet
Can for-each modify array elements?NoLoop variable is a copy โ€” assignment doesn't affect array
System.out.println(arr) prints?Hash/type codeUse Arrays.toString(arr) to print values
Can an array hold different types?NoAll elements must be the same declared type

๐Ÿ“– One-Liner Definitions

  • Array โ€” Fixed-size, same-type, zero-indexed collection of elements stored contiguously in Heap.
  • array.length โ€” A field (not method) holding the fixed size of the array.
  • ArrayIndexOutOfBoundsException โ€” Runtime exception for accessing index < 0 or โ‰ฅ length.
  • for-each loop โ€” Enhanced loop for read-only iteration; cannot access index or modify elements.
  • Arrays.sort() โ€” Sorts a primitive array in ascending order in-place using Dual-Pivot Quicksort.
  • Arrays.toString() โ€” Returns a human-readable String representation of a 1D array.
  • 2D Array โ€” An array of arrays; each row is a separate Heap object with its own length.
TOPIC 06

Static Keyword

โšก What Does static Mean?

The static keyword in Java means a member belongs to the class itself, not to any particular object. It is loaded and initialized once when the class is first loaded by the JVM โ€” before any object is ever created.

Think of it this way: instance members are like personal belongings of each person (every object has their own copy). Static members are like shared facilities in an apartment building โ€” one copy shared by everyone.

๐Ÿ“Œ Two-Step JVM Process
When you reference a class for the first time, the JVM does two things in order:
Step 1 โ€” Class Loading: loads the .class bytecode, allocates Metaspace, runs static blocks, initializes static variables.
Step 2 โ€” Object Instantiation: only happens when you call new ClassName() โ€” runs the constructor, allocates Heap memory for the object.
Static members exist after Step 1 โ€” you never need Step 2 to access them.

๐Ÿ“Š Static Variable (Class Variable)

A static variable is shared across all instances of a class. There is exactly one copy in Metaspace, and every object reads and writes the same location.

STATIC vs INSTANCE VARIABLE โ€” MEMORY โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Counter { static int count = 0; // ONE copy in Metaspace int id; // EACH object gets its own in Heap } Counter c1 = new Counter(); Counter c2 = new Counter(); METASPACE HEAP โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Counter.count โ”‚ โ”‚ c1 object โ”‚ โ”‚ c2 object โ”‚ โ”‚ = 2 โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ id = 1 โ”‚ โ”‚ id = 2 โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ–ฒ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ Both objects share the SAME count! c1.count = 5 โ†’ c2.count is ALSO 5 Counter.count = 5 โ† preferred โ€” use class name, not object
โš ๏ธ Access static members via class name, not object reference
c1.count works but is misleading โ€” it looks like c1 owns it. Always write Counter.count to make it clear this is a class-level member. Most IDEs show a warning when you access static members via object reference.

๐Ÿงฑ Static Block

A static block (also called a static initializer) is a block of code that runs once when the class is loaded โ€” before any constructor, before main(), before any object is created. Used for complex static initialization that cannot be done in a single line.

CLASS LOADING ORDER โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Demo { static int x; static { // โ† runs FIRST, when class loads x = computeX(); System.out.println("Static block"); } Demo() { // โ† runs on each new Demo() System.out.println("Constructor"); } } Execution order when you write: new Demo(); new Demo(); โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 1. Static block (once only โ€” class load time) โ”‚ โ”‚ 2. Constructor (first new Demo()) โ”‚ โ”‚ 3. Constructor (second new Demo()) โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ Static block NEVER runs again, no matter how many objects!

๐Ÿ”ง Static Method

A static method belongs to the class and can be called without creating an object. It has these restrictions:

  • Cannot access instance variables directly (no this)
  • Cannot call instance methods directly
  • Can only directly access other static members
  • Can access instance members if given an object reference
WHY main() IS STATIC โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ public static void main(String[] args) โ–ฒ โ”‚ JVM calls main() to START the program. At that point, NO objects exist yet. JVM cannot call an instance method (needs object). So main() MUST be static โ€” callable without an object. โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Also: Math.sqrt(), Math.abs(), Integer.parseInt() All static โ€” you never do: new Math().sqrt(4)

๐Ÿ›๏ธ Static Class (Nested)

In Java you cannot make a top-level class static. However, a nested class can be declared static โ€” a static nested class does not need an instance of the outer class to be instantiated (covered in detail in Topic 24: Inner Classes).

๐Ÿ“‹ Summary โ€” What static Can Be Applied To

Applied ToEffectExample
VariableOne shared copy in Metaspace for all objectsstatic int count;
MethodCallable without object; no this accessstatic void help()
BlockRuns once at class load time before constructorsstatic { ... }
Nested classInner class with no dependency on outer class instancestatic class Node { }
ImportImport static members directly โ€” use without class prefiximport static java.lang.Math.*;

๐Ÿ’ป Program 1 โ€” Static Variable as Object Counter

Employee.java
public class Employee {

    // static โ€” ONE copy shared by all Employee objects
    static int count = 0;

    // instance โ€” each Employee has their own
    String name;
    int    id;

    Employee(String name) {
        this.name = name;
        count++;            // increments the SHARED counter
        this.id = count;     // auto-assign unique id
    }

    void display() {
        System.out.printf("ID: %d | Name: %s%n", id, name);
    }

    public static void main(String[] args) {
        Employee e1 = new Employee("Rahul");
        Employee e2 = new Employee("Priya");
        Employee e3 = new Employee("Arjun");

        e1.display();
        e2.display();
        e3.display();

        // Access via class name โ€” correct way
        System.out.println("Total employees: " + Employee.count);
    }
}
โ–ถ OUTPUT
ID: 1 | Name: Rahul ID: 2 | Name: Priya ID: 3 | Name: Arjun Total employees: 3

๐Ÿ’ป Program 2 โ€” Static Block: Execution Order Proof

StaticBlockOrder.java
public class StaticBlockOrder {

    static int x;
    static int y;

    // Static block 1 โ€” runs first
    static {
        x = 10;
        System.out.println("Static block 1: x = " + x);
    }

    // Static block 2 โ€” runs second (multiple blocks allowed)
    static {
        y = x * 2;  // can use x โ€” block 1 already ran
        System.out.println("Static block 2: y = " + y);
    }

    StaticBlockOrder() {
        System.out.println("Constructor called");
    }

    public static void main(String[] args) {
        System.out.println("main() started");
        new StaticBlockOrder();
        new StaticBlockOrder();  // static blocks do NOT run again
    }
}
โ–ถ OUTPUT
Static block 1: x = 10 Static block 2: y = 20 main() started Constructor called Constructor called
๐Ÿ“Œ Static blocks run BEFORE main()
Notice the output โ€” static blocks printed before "main() started". This surprises many developers. The JVM loads the class (running static blocks) before executing main(). Even main() itself lives in a class that must first be loaded.

๐Ÿ’ป Program 3 โ€” Static Method: Limitation Demonstration

StaticMethodDemo.java
public class StaticMethodDemo {

    String instanceVar = "I am instance";
    static String staticVar = "I am static";

    // Static method โ€” belongs to class
    static void staticMethod() {
        System.out.println(staticVar);         // โœ… can access static
        // System.out.println(instanceVar);   // โŒ compile error โ€” no 'this'

        // Workaround: pass object reference
        StaticMethodDemo obj = new StaticMethodDemo();
        System.out.println(obj.instanceVar);    // โœ… via explicit object
    }

    // Instance method โ€” has 'this', can access everything
    void instanceMethod() {
        System.out.println(instanceVar);  // โœ… both accessible
        System.out.println(staticVar);   // โœ…
    }

    public static void main(String[] args) {
        staticMethod();              // no object needed
        new StaticMethodDemo().instanceMethod();
    }
}
โ–ถ OUTPUT
I am static I am instance I am instance I am static

๐Ÿ’ป Program 4 โ€” Static Import

StaticImportDemo.java
// Without static import: Math.sqrt(16), Math.PI
// With static import: sqrt(16), PI  โ€” no Math. prefix needed
import static java.lang.Math.*;

public class StaticImportDemo {
    public static void main(String[] args) {
        System.out.println(sqrt(144));        // 12.0
        System.out.println(pow(2, 10));      // 1024.0
        System.out.println(PI);              // 3.141592653589793
        System.out.println(abs(-42));       // 42
    }
}
โ–ถ OUTPUT
12.0 1024.0 3.141592653589793 42
โš ๏ธ Overusing static is a design smell
Making everything static turns your OOP code into procedural code. Static state is shared globally, making it hard to test, hard to parallelize, and prone to bugs in multi-threaded apps. Use static only for: constants (static final), utility/helper methods that need no state (like Math), and factory methods. Avoid mutable static variables in production code.
๐Ÿ’ก Best Practice โ€” Constants with static final
The most common and correct use of static is declaring constants:
public static final double PI = 3.14159;
static = one copy shared. final = value never changes. Together they define a true constant. Name in ALL_CAPS by convention.
โญ Must-Know Interview Topics
static variable vs instance variable ยท static method limitations ยท why main() is static ยท static block execution order ยท static vs final ยท can we override static methods ยท static in multithreading
Q1. What is the difference between a static variable and an instance variable?
Static variable: declared with static. One copy stored in Metaspace, shared by all objects of the class. Created when the class loads, destroyed when class is unloaded. Accessed via class name: ClassName.variable.

Instance variable: no static. Each object gets its own copy in Heap. Created when object is created (new), destroyed when object is GC'd. Accessed via object reference: obj.variable.
Q2. Why is the main() method static?
Because the JVM needs to call main() to start the program โ€” at that moment, no objects exist yet. The JVM cannot call an instance method without an object. By making main() static, the JVM can invoke it directly on the class without creating any instance. The full signature public static void main(String[] args) is a JVM contract โ€” every part has a reason: public = JVM can access it, static = no object needed, void = no return value to JVM, String[] args = command-line arguments.
Q3. What are the restrictions of a static method?
A static method:
1. Cannot use this or super โ€” these refer to object instances which don't exist in a static context
2. Cannot directly access instance variables โ€” no implicit object
3. Cannot directly call instance methods
4. Can access instance members only via an explicit object reference
5. Can freely access other static variables and static methods
Q4. Can we override a static method in Java? (Very tricky!)
No โ€” static methods cannot be overridden. They can be hidden (method hiding), but that is not polymorphism.

When a subclass defines a static method with the same signature as a parent's static method, it hides the parent's version. The method called depends on the reference type at compile time, not the actual object type at runtime. True overriding involves runtime dispatch (polymorphism) โ€” static methods are resolved at compile time, so no runtime dispatch occurs.

Proof: @Override on a static method causes a compile error.
Q5. When does a static block run? Can a class have multiple static blocks?
A static block runs once, when the class is first loaded by the JVM โ€” before any constructor runs and before main() executes. It runs exactly once per class load, regardless of how many objects are created.

Yes, a class can have multiple static blocks. They run in the order they appear in the source code, top to bottom. Each subsequent block can use values set by earlier blocks.
Q6. What is the difference between static and final?
static = belongs to the class, one shared copy, no object needed.
final = value/reference cannot be changed after assignment (for variables), method cannot be overridden, class cannot be subclassed.

They are completely independent but often combined: public static final int MAX = 100; โ€” this is the Java idiom for a constant: shared across all instances (static) and never reassignable (final).
Q7. Are static variables thread-safe?
No โ€” static variables are NOT thread-safe by default. Since static variables are shared across all threads (Metaspace is shared), concurrent read-modify-write operations on a static variable can cause race conditions.

Solutions: use synchronized blocks/methods, use AtomicInteger / AtomicLong (from java.util.concurrent.atomic), or avoid mutable static state altogether. This is one of the reasons overusing static is a design smell in multi-threaded applications.

๐ŸŒฑ static in Spring Boot

Spring Boot generally discourages mutable static state because it breaks testability and thread safety. However, static final constants and specific patterns are common and correct.

โœ… Correct uses of static in Spring Boot
Constants โ€” public static final String ROLE_ADMIN = "ROLE_ADMIN";
Logger โ€” private static final Logger log = LoggerFactory.getLogger(MyClass.class);
Utility methods โ€” stateless helper methods that don't need Spring-managed state.
@Value workaround โ€” injecting into static field via setter (shown below).
AppConstants.java + LoggerDemo.java โ€” Spring Boot
// โ”€โ”€ Constants class โ€” all static final โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
public class AppConstants {
    public static final String ROLE_ADMIN   = "ROLE_ADMIN";
    public static final String ROLE_USER    = "ROLE_USER";
    public static final int    MAX_PAGE_SIZE = 50;
    private AppConstants() {}  // prevent instantiation
}

// โ”€โ”€ Logger โ€” standard Spring Boot pattern โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@Service
public class OrderService {

    // static final logger โ€” one per class, shared, immutable
    private static final Logger log =
        LoggerFactory.getLogger(OrderService.class);

    public void placeOrder(Order order) {
        log.info("Placing order for user: {}", order.getUserId());
        // ...
    }
}

// โ”€โ”€ @Value into static field (requires setter trick) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@Component
public class Config {
    public static String apiKey;

    @Value("${app.api-key}")
    public void setApiKey(String key) {
        Config.apiKey = key;  // @Value on setter, assigns to static field
    }
}
โš ๏ธ Never @Autowire into a static field directly
@Autowired static UserRepository repo; will always be null at runtime. Spring's dependency injection works on object instances โ€” it sets fields on the bean object after construction. Static fields belong to the class, not the object, so Spring never injects into them. Use the setter injection workaround shown above, or better yet, redesign to avoid static Spring beans entirely.
โญ Must-Know Exam Points
  • static = belongs to class, not object โ€” one copy in Metaspace
  • Static block runs once, at class load time, before constructor and before main()
  • Multiple static blocks run in top-to-bottom order
  • Static methods cannot use this, super, or access instance members directly
  • main() is static because JVM calls it before any object exists
  • Static methods cannot be overridden โ€” only hidden (method hiding)
  • Static variables are not thread-safe by default
  • Best use of static: static final constants, utility methods, loggers

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Can a static method access instance variables?Not directlyOnly via an explicit object reference โ€” no implicit this
How many copies of a static variable exist?OneShared across all objects โ€” one copy in Metaspace
When does a static block run?At class load timeBefore constructor AND before main() โ€” surprises many
Can static methods be overridden?NoThey are hidden, not overridden โ€” compile-time resolution
Can we use this in a static method?NoCompile error โ€” this is undefined in static context
Where are static variables stored?MetaspaceNot Heap, not Stack โ€” method area / Metaspace
Can an interface have static methods?Yes (Java 8+)Added in JDK 8 โ€” covered in Topic 26

๐Ÿ“– One-Liner Definitions

  • static variable โ€” Class-level variable with one shared copy in Metaspace; exists without any object.
  • static method โ€” Method callable without an object; cannot access instance members directly.
  • static block โ€” Initializer block that runs exactly once when the class is first loaded, before any constructor.
  • static final โ€” Java's idiom for a constant: one shared copy that can never be reassigned.
  • Method hiding โ€” When a subclass defines a static method with the same signature as a parent's static method; resolved at compile time, not runtime.
  • Static import โ€” Allows using static members without the class name prefix: import static java.lang.Math.*;
TOPIC 07

Encapsulation

๐Ÿงฑ What is Encapsulation?

Encapsulation is the OOP principle of binding data (fields) and the methods that operate on that data together into a single unit (class), while restricting direct access to the internal state from outside the class.

Think of a medicine capsule: the active ingredients (data) are sealed inside the capsule shell (class). You consume the capsule โ€” you don't directly touch or mix the ingredients. The capsule controls how its contents are delivered.

๐Ÿ“Œ Two Goals of Encapsulation
1. Data Hiding โ€” prevent external code from directly reading or modifying internal fields (use private).
2. Controlled Access โ€” expose fields only through well-defined public getter and setter methods that can validate input and enforce rules.

๐Ÿ”’ How to Achieve Encapsulation in Java

The recipe is always the same three steps:

ENCAPSULATION RECIPE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Step 1: Declare all instance variables as PRIVATE private String name; private int age; private double balance; Step 2: Provide PUBLIC getter methods (read access) public String getName() { return name; } public int getAge() { return age; } Step 3: Provide PUBLIC setter methods (write access + validation) public void setAge(int age) { if (age >= 0 && age <= 150) // validate! this.age = age; else throw new IllegalArgumentException("Invalid age"); } โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ External code CANNOT: obj.age = -5; // compile error External code CAN: obj.setAge(25); // controlled obj.setAge(-5); // throws exception

๐Ÿฆ Real-World Analogy โ€” Bank Account

A bank account is the classic encapsulation example. Your account balance is private โ€” you cannot walk into the bank and directly change it. All access goes through controlled methods: deposit(), withdraw(), getBalance(). The bank enforces rules (no negative balance, daily limits) inside those methods.

BANK ACCOUNT โ€” ENCAPSULATION MODEL โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— โ•‘ BankAccount class โ•‘ โ•‘ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ•‘ โ•‘ โ”‚ PRIVATE (hidden data) โ”‚ โ•‘ โ•‘ โ”‚ - accountNumber: String โ”‚ โ•‘ โ•‘ โ”‚ - balance: double โ”‚ โ•‘ โ•‘ โ”‚ - pin: int โ”‚ โ•‘ โ•‘ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ•‘ โ•‘ PUBLIC (controlled interface) โ•‘ โ•‘ + deposit(amount) โ•‘ โ•‘ + withdraw(amount) โ•‘ โ•‘ + getBalance() โ•‘ โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• โ–ฒ External code only sees the public interface!

๐Ÿ” Encapsulation vs Abstraction โ€” The Critical Difference

AspectEncapsulationAbstraction
GoalHide the dataHide the implementation complexity
What is hiddenInternal state (fields)Internal logic (how it works)
How achievedprivate fields + getters/settersAbstract classes, Interfaces
FocusData protection & controlSimplifying usage
LevelClass levelDesign level
AnalogyATM hides your balance as privateATM hides the banking software internals

โœ… Benefits of Encapsulation

  • Data Integrity โ€” setters validate input before changing state, preventing invalid data
  • Flexibility โ€” internal implementation can change without affecting external code
  • Maintainability โ€” changes are localized inside the class
  • Reusability โ€” well-encapsulated classes are easier to reuse safely
  • Testability โ€” controlled access makes unit testing predictable
  • Security โ€” sensitive data (passwords, PINs) stays hidden

๐Ÿ“ Getter and Setter Naming Convention

GETTER / SETTER NAMING RULES (JavaBean Convention) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Field: private String firstName; Getter: public String getFirstName() // get + FieldName Setter: public void setFirstName(String fn) // set + FieldName For boolean fields โ€” getter uses "is" prefix: Field: private boolean active; Getter: public boolean isActive() // is + FieldName Setter: public void setActive(boolean a) Why this matters: - IDEs auto-generate using this convention - Frameworks (Spring, Hibernate, Jackson) rely on this naming to discover fields automatically via reflection - Jackson maps JSON {"firstName":"Rahul"} to setFirstName("Rahul")

๐Ÿ’ป Program 1 โ€” Fully Encapsulated BankAccount

BankAccount.java
public class BankAccount {

    // Step 1: private fields โ€” no direct outside access
    private String owner;
    private double balance;
    private int    pin;

    // Constructor
    public BankAccount(String owner, double initialDeposit, int pin) {
        this.owner   = owner;
        this.balance = initialDeposit;
        this.pin     = pin;
    }

    // Step 2: getter โ€” read-only for balance
    public String getOwner()   { return owner; }
    public double getBalance() { return balance; }
    // No getter for pin โ€” completely hidden!

    // Step 3: controlled operations with validation
    public void deposit(double amount) {
        if (amount <= 0)
            throw new IllegalArgumentException("Deposit must be positive");
        balance += amount;
        System.out.printf("Deposited โ‚น%.2f | New balance: โ‚น%.2f%n", amount, balance);
    }

    public void withdraw(double amount, int enteredPin) {
        if (enteredPin != pin) {
            System.out.println("โŒ Wrong PIN!");
            return;
        }
        if (amount > balance) {
            System.out.println("โŒ Insufficient funds!");
            return;
        }
        balance -= amount;
        System.out.printf("Withdrew โ‚น%.2f | New balance: โ‚น%.2f%n", amount, balance);
    }

    public static void main(String[] args) {
        BankAccount acc = new BankAccount("Rahul", 5000.0, 1234);

        // acc.balance = 99999; // โŒ compile error โ€” private!
        // acc.pin = 0000;      // โŒ compile error โ€” private!

        acc.deposit(1500.0);
        acc.withdraw(2000.0, 9999);   // wrong PIN
        acc.withdraw(2000.0, 1234);   // correct PIN
        acc.withdraw(9000.0, 1234);   // insufficient funds
        System.out.println("Final balance: โ‚น" + acc.getBalance());
    }
}
โ–ถ OUTPUT
Deposited โ‚น1500.00 | New balance: โ‚น6500.00 โŒ Wrong PIN! Withdrew โ‚น2000.00 | New balance: โ‚น4500.00 โŒ Insufficient funds! Final balance: โ‚น4500.0

๐Ÿ’ป Program 2 โ€” Student with Validated Setters

Student.java
public class Student {

    private String name;
    private int    age;
    private double marks;
    private boolean passed;     // derived โ€” no setter needed

    // Getters
    public String  getName()   { return name; }
    public int     getAge()    { return age; }
    public double  getMarks()  { return marks; }
    public boolean isPassed()  { return passed; }   // "is" for boolean

    // Setters with validation
    public void setName(String name) {
        if (name == null || name.isBlank())
            throw new IllegalArgumentException("Name cannot be blank");
        this.name = name.trim();
    }

    public void setAge(int age) {
        if (age < 1 || age > 120)
            throw new IllegalArgumentException("Age out of range: " + age);
        this.age = age;
    }

    public void setMarks(double marks) {
        if (marks < 0 || marks > 100)
            throw new IllegalArgumentException("Marks must be 0-100");
        this.marks  = marks;
        this.passed = marks >= 35;   // auto-derived field
    }

    public static void main(String[] args) {
        Student s = new Student();
        s.setName("Rahul");
        s.setAge(20);
        s.setMarks(72.5);

        System.out.printf("%s | Age: %d | Marks: %.1f | Passed: %b%n",
            s.getName(), s.getAge(), s.getMarks(), s.isPassed());

        try {
            s.setAge(-5);   // triggers validation
        } catch (IllegalArgumentException e) {
            System.out.println("โŒ Error: " + e.getMessage());
        }
    }
}
โ–ถ OUTPUT
Rahul | Age: 20 | Marks: 72.5 | Passed: true โŒ Error: Age out of range: -5

๐Ÿ’ป Program 3 โ€” Read-Only and Write-Only Fields

AccessControl.java
public class AccessControl {

    private final String id;       // READ-ONLY: getter only, no setter
    private       String password; // WRITE-ONLY: setter only, no getter
    private       String email;    // READ-WRITE: both getter and setter

    public AccessControl(String id, String password, String email) {
        this.id       = id;
        this.password = password;
        this.email    = email;
    }

    // READ-ONLY โ€” getter, no setter
    public String getId() { return id; }

    // WRITE-ONLY โ€” setter, no getter (password stays hidden)
    public void setPassword(String newPwd) {
        if (newPwd.length() < 8)
            throw new IllegalArgumentException("Password too short");
        this.password = newPwd;  // stored as-is (use BCrypt in real apps)
    }

    // READ-WRITE โ€” both
    public String getEmail()           { return email; }
    public void   setEmail(String email) { this.email = email; }

    public static void main(String[] args) {
        AccessControl user = new AccessControl("USR001", "secret99", "r@mail.com");
        System.out.println("ID: "    + user.getId());
        System.out.println("Email: " + user.getEmail());
        // user.getId() works; user.id = "X" โ†’ compile error
        // user.password โ†’ compile error (write-only)
        user.setPassword("newSecure123");
        System.out.println("Password updated successfully");
    }
}
โ–ถ OUTPUT
ID: USR001 Email: r@mail.com Password updated successfully
๐Ÿ’ก IDE Shortcut โ€” Generate Getters & Setters
In IntelliJ IDEA: Right-click โ†’ Generate โ†’ Getter and Setter (or Alt+Insert)
In Eclipse: Right-click โ†’ Source โ†’ Generate Getters and Setters
In VS Code: install the Java Extension Pack โ€” same right-click option appears.
Always review generated code โ€” IDEs don't add validation logic.
โš ๏ธ Returning mutable objects from getters breaks encapsulation
If a field is a mutable object (like an array or ArrayList), returning it directly from a getter exposes the internal reference โ€” callers can modify it without going through setters:
public List<String> getTags() { return tags; } โ† caller can call getTags().clear()!
Fix: return a defensive copy: return new ArrayList<>(tags); or return an unmodifiable view: return Collections.unmodifiableList(tags);
โญ Must-Know Interview Topics
Definition with example ยท Encapsulation vs Abstraction ยท Why private fields ยท Benefits ยท Getter/setter naming (JavaBean) ยท Read-only and write-only fields ยท Defensive copying
Q1. What is Encapsulation? Give a real-world example.
Encapsulation is the OOP principle of bundling data and methods together in a class while hiding the internal data from outside access using access modifiers.

Real-world example: A TV remote. You press buttons (public methods) to change the channel or volume. You cannot directly access or modify the internal circuit board (private fields). The remote's internals are encapsulated โ€” you interact only through the defined interface (buttons).

In Java: make fields private, expose via public getters and setters.
Q2. What is the difference between Encapsulation and Abstraction?
Encapsulation hides data โ€” it wraps fields as private and controls access through getters/setters. It answers: "How do I protect internal state?"

Abstraction hides implementation complexity โ€” it exposes only a clean interface, hiding how things work internally. It answers: "What should the user see?"

Simple memory trick: Encapsulation = data hiding (field level). Abstraction = implementation hiding (design level). A class can implement both simultaneously โ€” private fields (encapsulation) + interface-based design (abstraction).
Q3. Why should fields be private? What happens if they are public?
If fields are public, any external code can set them to invalid values directly โ€” student.age = -500; student.marks = 999; โ€” and there is nothing the class can do to stop it. This breaks data integrity.

Making fields private forces all access through methods, which can validate input, enforce business rules, trigger side effects (like logging or updating derived fields), and change the internal implementation without breaking external code.
Q4. What are the JavaBean naming conventions for getters and setters?
Getter: get + field name with first letter capitalized. E.g., firstName โ†’ getFirstName()
Boolean getter: is + field name. E.g., active โ†’ isActive()
Setter: set + field name with first letter capitalized. E.g., firstName โ†’ setFirstName(String fn)

This convention matters because Spring, Hibernate, and Jackson use reflection to automatically discover and call getters/setters based on these naming patterns. Breaking the convention breaks framework auto-wiring, JSON serialization, and JPA column mapping.
Q5. Can we have a field with only a getter (read-only) or only a setter (write-only)?
Yes โ€” this is a valid and useful pattern.

Read-only field: provide a getter but no setter. Use private final + constructor initialization for true immutability. Example: auto-generated IDs, creation timestamps.

Write-only field: provide a setter but no getter. Used for sensitive data like passwords, PINs, or API keys โ€” external code can set them but can never read them back (they are only used internally for comparison).
Q6. What is a defensive copy and why is it needed in encapsulation?
A defensive copy is a new copy of a mutable object returned from a getter (or stored from a setter) to prevent external code from modifying the internal state directly.

Problem: public List<String> getItems() { return items; } โ€” caller gets the real internal list and can call getItems().clear(), bypassing all encapsulation.

Fix: return new ArrayList<>(items); โ€” caller gets a copy. Changes to the copy don't affect the original.

This is especially important for arrays, collections, and Date objects.
Q7. What is an Immutable class? How does it relate to encapsulation?
An immutable class is a class whose objects cannot be modified after creation. It is the strongest form of encapsulation. Rules to create one:
1. Declare class as final (prevent subclassing)
2. All fields private final
3. No setters โ€” only a constructor to set values
4. Return defensive copies from getters for mutable fields
5. Ensure no method modifies state

Java's String, Integer, LocalDate are all immutable. Immutable objects are inherently thread-safe โ€” no synchronization needed.

๐ŸŒฑ Encapsulation in Spring Boot

Spring Boot relies heavily on encapsulation. Every layer โ€” Entity, DTO, Service, Controller โ€” is a well-encapsulated class. Jackson (JSON library), JPA (database), and Spring DI all depend on the JavaBean convention of private fields + public getters/setters.

โœ… Key Encapsulation Patterns in Spring Boot
@Entity โ€” private fields mapped to DB columns, accessed via getters/setters by JPA.
DTO (Data Transfer Object) โ€” encapsulates only the fields needed for API request/response, hiding internal entity fields.
@ConfigurationProperties โ€” encapsulates config values as a typed, validated class.
Service layer โ€” encapsulates business logic, hides repository details from controllers.
UserEntity + UserDTO โ€” Spring Boot encapsulation pattern
// โ”€โ”€ Entity โ€” private fields, full getter/setter for JPA โ”€โ”€โ”€โ”€โ”€
@Entity
public class User {
    @Id @GeneratedValue
    private Long   id;
    private String name;
    private String email;
    private String passwordHash;  // never exposed to API

    public Long   getId()           { return id; }
    public String getName()         { return name; }
    public String getEmail()        { return email; }
    public void   setName(String n) { this.name = n; }
    public void   setEmail(String e) { this.email = e; }
    // No getter for passwordHash โ€” encapsulated completely
}

// โ”€โ”€ DTO โ€” only expose what the API consumer should see โ”€โ”€โ”€โ”€โ”€โ”€
public class UserResponseDTO {
    private Long   id;
    private String name;
    private String email;
    // passwordHash intentionally NOT included

    public UserResponseDTO(User user) {
        this.id    = user.getId();
        this.name  = user.getName();
        this.email = user.getEmail();
    }
    public Long   getId()    { return id; }
    public String getName()  { return name; }
    public String getEmail() { return email; }
}
๐Ÿ“Œ Lombok โ€” Eliminate Boilerplate
In real Spring Boot projects, developers use Lombok to auto-generate getters, setters, constructors, and more at compile time via annotations:
@Getter @Setter โ€” generates all getters and setters
@Data โ€” generates getters, setters, equals, hashCode, toString
@Value โ€” generates an immutable class (all fields private final, no setters)
@Builder โ€” generates a builder pattern
Lombok does not skip encapsulation โ€” it just removes the verbosity of writing it manually.
โญ Must-Know Exam Points
  • Encapsulation = data hiding via private fields + public getters/setters
  • Achieved by: private fields + public getters/setters
  • Getter for boolean: isFieldName() not getFieldName()
  • Read-only = getter only | Write-only = setter only
  • Encapsulation hides data; Abstraction hides implementation
  • Encapsulation enables validation in setters โ€” impossible with public fields
  • Immutable class = strongest form of encapsulation
  • Frameworks (Spring, Jackson, JPA) rely on JavaBean naming convention

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Which modifier achieves data hiding?privateNot protected โ€” protected still allows subclass and package access
Getter for boolean active?isActive()Not getActive() โ€” boolean fields use is prefix by JavaBean convention
Which pillar does private fields + getters/setters implement?EncapsulationNot Abstraction โ€” encapsulation is about data hiding at field level
Can a class have a field with no getter AND no setter?YesA completely hidden field used only internally by the class is valid
What is an immutable class?No state change after creationRequires private final fields, no setters, and defensive copies for mutable fields
Is String encapsulated in Java?Yes โ€” and immutableAll String internals are private; you can only use its public methods

๐Ÿ“– One-Liner Definitions

  • Encapsulation โ€” Bundling data and methods in a class while restricting direct field access using private.
  • Getter โ€” A public method that returns the value of a private field; named getField() or isField() for booleans.
  • Setter โ€” A public method that sets the value of a private field, optionally with validation; named setField().
  • Data Hiding โ€” Preventing external code from directly accessing or modifying internal state.
  • Defensive Copy โ€” A fresh copy of a mutable object returned from a getter to prevent external mutation of internal state.
  • Immutable Class โ€” A class whose instances cannot change state after construction; the strongest form of encapsulation.
  • JavaBean Convention โ€” Standard naming rules for getters/setters used by frameworks: get/set/is + CapitalizedFieldName.
TOPIC 08

this Keyword

๐Ÿ”‘ What is this?

this is a reference variable that refers to the current object โ€” the object whose method or constructor is currently executing. It is implicitly available in every instance method and constructor. It is not available in static methods (no current object exists there).

๐Ÿ“Œ this is a hidden parameter
When you call s1.printInfo(), the JVM secretly passes s1 as a hidden first argument into the method. Inside the method, that hidden reference is named this. This is how every instance method knows which object's data to operate on.

๐Ÿ“‹ Four Uses of this

Use 1 โ€” Resolve Name Conflict (Instance Variable vs Parameter)

The most common use. When a constructor or method parameter has the same name as an instance variable, this.varName refers to the instance variable, and plain varName refers to the parameter.

NAME CONFLICT RESOLUTION โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Student { String name; // instance variable Student(String name) { // parameter also named "name" name = name; // โŒ WRONG โ€” assigns param to itself! this.name = name; // โœ… RIGHT โ€” instance var = param } } โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Without this.name = name โ†’ instance variable stays null this.name refers to the current object's name field plain name refers to the constructor parameter

Use 2 โ€” Constructor Chaining with this()

You can call one constructor of the same class from another using this(args). This avoids duplicating initialization code across multiple constructors.

CONSTRUCTOR CHAINING WITH this() โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Box { int l, w, h; Box(int l, int w, int h) { // master constructor this.l=l; this.w=w; this.h=h; } Box(int side) { // delegates to master this(side, side, side); // โ† this() call โ€” FIRST LINE! } Box() { // delegates to master this(1, 1, 1); // โ† default cube } } โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ RULE: this() must be the FIRST statement in the constructor. Cannot use both this() and super() in the same constructor.

Use 3 โ€” Pass Current Object as Argument

You can pass this to another method or constructor that expects the current object type. Useful for event-handling, callbacks, and builder patterns.

PASSING this AS ARGUMENT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Printer { void print(Student s) { System.out.println("Printing: " + s.name); } } class Student { String name = "Rahul"; void sendToPrinter() { Printer p = new Printer(); p.print(this); // pass current Student object } }

Use 4 โ€” Return Current Object (Method Chaining / Builder)

When a method returns this, the caller can chain multiple method calls on the same object in a single expression โ€” this is the foundation of the Builder Pattern and fluent APIs like StringBuilder.

RETURNING this FOR METHOD CHAINING โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Builder { String name; int age; Builder setName(String n) { this.name = n; return this; } Builder setAge(int a) { this.age = a; return this; } void build() { System.out.println(name + ", " + age); } } new Builder().setName("Rahul").setAge(25).build(); // โ””โ”€โ”€ returns this โ”€โ”€โ”˜โ””โ”€โ”€ returns this โ”€โ”€โ”˜

โš ๏ธ Where this Cannot Be Used

  • Static methods โ€” compile error. No current object exists in a static context.
  • Static blocks โ€” same reason. No object is being constructed.
  • Outside a class โ€” meaningless without a class context.

๐Ÿ“Š Summary of this Uses

UseSyntaxPurpose
Resolve name conflictthis.fieldNameDistinguish instance var from local/param with same name
Constructor chainingthis(args)Call another constructor of same class โ€” must be first line
Pass current objectmethodCall(this)Send current object to another method as argument
Return current objectreturn this;Enable method chaining / fluent API / Builder pattern

๐Ÿ’ป Program 1 โ€” this to Resolve Name Conflict

NameConflict.java
public class Student {
    String name;
    int    age;
    double marks;

    // Without this โ†’ name conflict bug
    Student(String name, int age, double marks) {
        this.name  = name;   // instance var = parameter
        this.age   = age;
        this.marks = marks;
    }

    void display() {
        // this. optional here (no conflict) but makes intent clear
        System.out.printf("Name: %s | Age: %d | Marks: %.1f%n",
            this.name, this.age, this.marks);
    }

    public static void main(String[] args) {
        new Student("Rahul", 20, 88.5).display();
        new Student("Priya", 22, 94.0).display();
    }
}
โ–ถ OUTPUT
Name: Rahul | Age: 20 | Marks: 88.5 Name: Priya | Age: 22 | Marks: 94.0

๐Ÿ’ป Program 2 โ€” Constructor Chaining with this()

ConstructorChain.java
public class Box {
    int length, width, height;

    // Master constructor โ€” does all the real work
    Box(int length, int width, int height) {
        this.length = length;
        this.width  = width;
        this.height = height;
        System.out.println("Box("+length+","+width+","+height+") created");
    }

    // Cube shortcut โ€” delegates to master
    Box(int side) {
        this(side, side, side);  // MUST be first line
        System.out.println("Cube shortcut used");
    }

    // Default 1x1x1 box
    Box() {
        this(1);  // chains to Box(int side)
        System.out.println("Default box used");
    }

    int volume() { return length * width * height; }

    public static void main(String[] args) {
        System.out.println("--- Custom Box ---");
        Box b1 = new Box(3, 4, 5);
        System.out.println("Volume: " + b1.volume());

        System.out.println("--- Cube ---");
        Box b2 = new Box(4);
        System.out.println("Volume: " + b2.volume());

        System.out.println("--- Default ---");
        Box b3 = new Box();
        System.out.println("Volume: " + b3.volume());
    }
}
โ–ถ OUTPUT
--- Custom Box --- Box(3,4,5) created Volume: 60 --- Cube --- Box(4,4,4) created Cube shortcut used Volume: 64 --- Default --- Box(1,1,1) created Cube shortcut used Default box used Volume: 1

๐Ÿ’ป Program 3 โ€” Passing this as Argument

PassThisDemo.java
class Validator {
    boolean isValid(BankAccount acc) {
        return acc.balance >= 0 && acc.owner != null;
    }
}

class BankAccount {
    String owner;
    double balance;

    BankAccount(String owner, double balance) {
        this.owner   = owner;
        this.balance = balance;
    }

    void validate() {
        Validator v = new Validator();
        boolean ok = v.isValid(this);  // pass current object
        System.out.println(owner + " account valid: " + ok);
    }
}

public class PassThisDemo {
    public static void main(String[] args) {
        new BankAccount("Rahul", 5000).validate();
        new BankAccount(null, -100).validate();
    }
}
โ–ถ OUTPUT
Rahul account valid: true null account valid: false

๐Ÿ’ป Program 4 โ€” Return this for Fluent Builder

FluentBuilder.java
public class QueryBuilder {
    String table, condition, orderBy;
    int    limit = 10;

    QueryBuilder from(String table) {
        this.table = table; return this;
    }
    QueryBuilder where(String condition) {
        this.condition = condition; return this;
    }
    QueryBuilder orderBy(String col) {
        this.orderBy = col; return this;
    }
    QueryBuilder limit(int n) {
        this.limit = n; return this;
    }

    String build() {
        return "SELECT * FROM " + table
             + " WHERE "    + condition
             + " ORDER BY " + orderBy
             + " LIMIT "    + limit;
    }

    public static void main(String[] args) {
        String query = new QueryBuilder()
            .from("users")
            .where("active = true")
            .orderBy("name")
            .limit(5)
            .build();
        System.out.println(query);
    }
}
โ–ถ OUTPUT
SELECT * FROM users WHERE active = true ORDER BY name LIMIT 5
โš ๏ธ this() must be the FIRST statement in constructor
If you put any statement before this(args) in a constructor, you get a compile error: "Constructor call must be the first statement in a constructor". The same rule applies to super(). And you cannot have both this() and super() as the first line โ€” only one is allowed.
๐Ÿ’ก Tip โ€” Name your parameters the same as fields
Always name constructor/setter parameters the same as the instance variable they set, and use this.field = field. This makes the mapping crystal clear, is the universal Java convention, and is what IDEs generate automatically. Never use misleading names like setName(String n) when you could use setName(String name).
โญ Must-Know Interview Topics
Four uses of this ยท this() rules ยท this vs super ยท this in static context ยท constructor chaining chain order ยท Builder pattern using this
Q1. What is the this keyword in Java? What are its uses?
this is a reference to the current object inside an instance method or constructor. Its four uses:
1. Resolve name conflict: this.name = name โ€” distinguish instance variable from parameter
2. Constructor chaining: this(args) โ€” call another constructor of same class (must be first line)
3. Pass current object: method(this) โ€” send current object as argument to another method
4. Return current object: return this โ€” enable method chaining / fluent APIs
Q2. What is constructor chaining? What are the rules for this()?
Constructor chaining is calling one constructor from another to avoid code duplication. Within the same class, use this(args).

Rules:
1. this() must be the first statement in the constructor body
2. You cannot have both this() and super() as the first line โ€” only one
3. Cannot create circular chains (A calls B, B calls A) โ€” infinite loop detected at compile time
4. The chain must eventually reach a constructor that does NOT call another constructor
Q3. Can we use this in a static method?
No โ€” compile error. this refers to the current object instance. Static methods belong to the class and execute without any object context โ€” there is no "current object" when a static method runs. Writing this inside a static method causes: "Cannot use this in a static context".
Q4. What is the difference between this() and super()?
this(): calls another constructor of the same class. Used for constructor overloading / chaining within one class.

super(): calls a constructor of the parent class. Used to initialize the parent part of the object. If you don't write it explicitly, the compiler inserts super() automatically as the first line of every constructor.

Both must be the first line โ€” so they are mutually exclusive in any single constructor.
Q5. What is the Builder Pattern? How does this enable it?
The Builder Pattern constructs complex objects step by step via a fluent chain of method calls, avoiding large constructors with many parameters (telescoping constructor problem).

It works by having each setter-style method assign a value and return this โ€” returning the same object each time so the next method call operates on the same instance. The chain ends with a build() or terminal method.

This pattern is heavily used in Spring Boot โ€” ResponseEntity.ok().header(...).body(...), MockMvcRequestBuilders.get(...).param(...).accept(...), and Lombok's @Builder annotation all use it.
Q6. What happens if you write name = name instead of this.name = name in a constructor?
Silent bug โ€” the instance variable stays at its default value. When both the parameter and instance variable have the same name, the parameter (local scope) shadows the instance variable. Writing name = name simply assigns the parameter back to itself โ€” the instance variable is never touched and remains null (for String) or 0 (for int). No error is thrown โ€” this is one of the most common beginner bugs in Java.
Q7. Tricky: Does this create a new object or reuse the current one?
this never creates a new object. It is simply a reference (alias) pointing to the already-existing current object. When you write return this, you're returning a reference to the existing object โ€” not a copy, not a new object. This is what makes method chaining memory-efficient: the same Heap object is reused across the entire chain.

๐ŸŒฑ this in Spring Boot

The this keyword appears constantly in Spring Boot โ€” in constructors for dependency injection, in builder-style response construction, and in entity design. Understanding it well makes Spring code more natural to read and write.

โœ… Most common this patterns in Spring Boot
Constructor injection โ€” this.repo = repo in every Spring service/controller
ResponseEntity builder โ€” Spring's fluent HTTP response builder uses return this internally
@Builder (Lombok) โ€” auto-generates Builder pattern with return this for entity classes
this() in entity constructors โ€” chain overloaded constructors to avoid duplicating JPA logic
UserController.java + User.java โ€” Spring Boot
// โ”€โ”€ Constructor injection โ€” this.field = field โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@RestController
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;  // classic this use
    }

    @GetMapping("/users/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        // ResponseEntity uses return this internally โ€” fluent builder
        return ResponseEntity.ok(userService.findById(id));
    }
}

// โ”€โ”€ Lombok @Builder โ€” auto-generates return this setters โ”€โ”€โ”€โ”€
@Entity
@Builder        // Lombok generates full Builder class
@NoArgsConstructor
@AllArgsConstructor
public class User {
    @Id @GeneratedValue
    private Long   id;
    private String name;
    private String email;
}

// Usage โ€” Lombok Builder (uses this internally):
// User u = User.builder().name("Rahul").email("r@x.com").build();
โญ Must-Know Exam Points
  • this = reference to the current object inside instance methods and constructors
  • this is not available in static methods โ€” compile error
  • this(args) = constructor chaining within same class โ€” must be first line
  • this() and super() are mutually exclusive โ€” only one can be first line
  • name = name without this is a silent bug โ€” parameter assigns to itself
  • return this enables method chaining / Builder pattern
  • this never creates a new object โ€” it references the existing one

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Can this be used in a static method?No โ€” compile errorNo current object in static context
Where must this() appear in a constructor?First lineAny statement before it causes compile error
Can both this() and super() be in one constructor?NoBoth must be first line โ€” mutually exclusive
What does return this; do?Returns current objectNot a copy โ€” the same Heap object reference
What is name = name without this?Silent bugParameter assigned to itself; instance var stays default
Does this() call the parent constructor?Nothis() = same class. super() = parent class constructor

๐Ÿ“– One-Liner Definitions

  • this โ€” A reference to the current object inside an instance method or constructor.
  • this.field โ€” Accesses the instance variable of the current object, resolving shadowing by local variables.
  • this(args) โ€” Calls another constructor of the same class; must be the first statement.
  • Constructor chaining โ€” Calling one constructor from another to reuse initialization logic.
  • Method chaining โ€” Calling multiple methods on the same object in sequence, enabled by each method returning this.
  • Builder Pattern โ€” A design pattern using return this to build complex objects step by step via a fluent API.
TOPIC 09

Constructors โ€” Fundamentals & Types

๐Ÿ—๏ธ What is a Constructor?

A constructor is a special method-like block that is automatically called when an object is created using new. Its job is to initialise the object's state โ€” setting instance variables to meaningful starting values before the object is used.

๐Ÿ“Œ Constructor vs Method โ€” Key Differences
ConstructorMethod
Same name as the classAny valid identifier name
No return type โ€” not even voidMust have a return type (or void)
Called automatically on newCalled explicitly by the programmer
Cannot be called again after object creationCan be called any number of times
Cannot be inherited (but can be chained via super)Can be inherited and overridden
Cannot be static, abstract, or finalCan be any modifier

1๏ธโƒฃ Default Constructor

If you write no constructor at all in your class, the Java compiler automatically inserts a default constructor โ€” a no-argument constructor with an empty body. It simply calls super() and lets instance variables take their default values (0, false, null).

DEFAULT CONSTRUCTOR โ€” COMPILER-GENERATED โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ What you write: What compiler generates: โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ class Dog { โ”‚ โ”‚ class Dog { โ”‚ โ”‚ String name; โ”‚ โ”€โ”€โ–บ โ”‚ String name; โ”‚ โ”‚ int age; โ”‚ โ”‚ int age; โ”‚ โ”‚ } โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ Dog() { โ† inserted! โ”‚ โ”‚ super(); โ† also inserted โ”‚ โ”‚ } โ”‚ โ”‚ } โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ Dog d = new Dog(); โ† calls the compiler-generated constructor d.name = null, d.age = 0 โ† default values
โš ๏ธ Default constructor disappears when you define any constructor
The moment you write any constructor (even a parameterized one), the compiler no longer inserts the default constructor. If you still need a no-arg constructor (required by JPA entities, many frameworks), you must write it explicitly.

2๏ธโƒฃ Parameterized Constructor

A constructor that accepts arguments, allowing each object to be initialised with specific values at creation time. This is the most commonly used constructor type.

PARAMETERIZED CONSTRUCTOR FLOW โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Student s = new Student("Rahul", 20); โ”‚ โ–ผ JVM allocates Heap memory for a Student object โ”‚ โ–ผ Constructor Student(String name, int age) executes: this.name = "Rahul" โ† parameter โ†’ instance variable this.age = 20 โ”‚ โ–ผ Reference s on Stack points to the initialised Heap object โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

3๏ธโƒฃ Constructor Overloading

Just like method overloading, you can define multiple constructors in one class as long as each has a different parameter list (different number, types, or order of parameters). The JVM calls the matching one based on the arguments provided at the call site.

CONSTRUCTOR OVERLOADING โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Student { Student() // โ† no-arg Student(String name) // โ† name only Student(String name, int age) // โ† name + age Student(String name, int age, double marks) // โ† full } new Student() โ†’ calls constructor 1 new Student("Rahul") โ†’ calls constructor 2 new Student("Rahul", 20) โ†’ calls constructor 3 new Student("Rahul", 20, 88.5) โ†’ calls constructor 4 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Resolution: resolved at COMPILE TIME based on argument types Use this() chaining to avoid duplicating init logic!

๐Ÿ”„ Object Creation โ€” Full Sequence

COMPLETE OBJECT CREATION SEQUENCE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Student s = new Student("Rahul", 20); Step 1: Class loading (if not already loaded) โ†’ static blocks run, static vars initialised Step 2: Memory allocation โ†’ JVM allocates Heap space for Student object โ†’ all instance vars set to defaults (null/0/false) Step 3: Instance initialisers run (if any) Step 4: Constructor executes โ†’ super() called first (even if not written) โ†’ constructor body runs, assigns values Step 5: Reference returned โ†’ s on Stack now points to ready object in Heap โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
๐Ÿ’ก Tip โ€” Always provide a no-arg constructor alongside parameterized ones
Most Java frameworks (JPA/Hibernate, Jackson JSON, Spring) require a no-argument constructor to instantiate objects via reflection. When you only define parameterized constructors, frameworks that use Class.newInstance() will throw an exception. Best practice: always explicitly add a no-arg constructor when you also define parameterized ones.

๐Ÿ’ป Program 1 โ€” Default vs Parameterized Constructor

ConstructorTypes.java
public class Car {
    String brand;
    String color;
    int    year;

    // 1. No-arg constructor โ€” explicit (needed alongside parameterized)
    Car() {
        this.brand = "Unknown";
        this.color = "White";
        this.year  = 2020;
        System.out.println("No-arg constructor called");
    }

    // 2. Parameterized constructor
    Car(String brand, String color, int year) {
        this.brand = brand;
        this.color = color;
        this.year  = year;
        System.out.println("Parameterized constructor called");
    }

    void display() {
        System.out.printf("  %s | %s | %d%n", brand, color, year);
    }

    public static void main(String[] args) {
        Car c1 = new Car();                        // no-arg
        c1.display();

        Car c2 = new Car("Toyota", "Red", 2023);  // parameterized
        c2.display();
    }
}
โ–ถ OUTPUT
No-arg constructor called Unknown | White | 2020 Parameterized constructor called Toyota | Red | 2023

๐Ÿ’ป Program 2 โ€” Constructor Overloading with this() Chaining

OverloadedConstructors.java
public class Product {
    String name;
    double price;
    int    stock;
    String category;

    // Master constructor โ€” all four fields
    Product(String name, double price, int stock, String category) {
        this.name     = name;
        this.price    = price;
        this.stock    = stock;
        this.category = category;
    }

    // No category โ€” delegates, uses "General" as default
    Product(String name, double price, int stock) {
        this(name, price, stock, "General");
    }

    // Name + price only โ€” stock defaults to 0
    Product(String name, double price) {
        this(name, price, 0);
    }

    // Name only โ€” placeholder product
    Product(String name) {
        this(name, 0.0);
    }

    void display() {
        System.out.printf("%-12s | โ‚น%-8.2f | stock:%-4d | %s%n",
            name, price, stock, category);
    }

    public static void main(String[] args) {
        new Product("Laptop", 75000, 10, "Electronics").display();
        new Product("Pen", 15, 500).display();
        new Product("Notebook", 120).display();
        new Product("TBD").display();
    }
}
โ–ถ OUTPUT
Laptop | โ‚น75000.00 | stock:10 | Electronics Pen | โ‚น15.00 | stock:500 | General Notebook | โ‚น120.00 | stock:0 | General TBD | โ‚น0.00 | stock:0 | General

๐Ÿ’ป Program 3 โ€” Proving Constructor Has No Return Type

ConstructorVsMethod.java
public class Demo {

    // TRUE constructor โ€” no return type, auto-called on new
    Demo() {
        System.out.println("Constructor: Demo()");
    }

    // This looks like a constructor but has void โ€” it is a METHOD!
    // Java allows methods with same name as class (confusing but legal)
    void Demo() {  // โ† this is a regular method, NOT a constructor
        System.out.println("Method: void Demo() โ€” I am a method, not a constructor!");
    }

    public static void main(String[] args) {
        Demo d = new Demo();  // only constructor runs
        d.Demo();              // explicit call runs the method
    }
}
โ–ถ OUTPUT
Constructor: Demo() Method: void Demo() โ€” I am a method, not a constructor!
โš ๏ธ A method with the same name as the class is NOT a constructor
Java allows a method to have the same name as its class โ€” this is legal but extremely confusing. The difference: a true constructor has no return type at all. If you see void Demo(), it is a regular method. Adding any return type (including void) strips it of constructor status โ€” it will never be called automatically on new.
๐Ÿ’ก Tip โ€” Use constructor chaining (this()) to keep DRY
Never copy-paste the same initialisation logic across multiple constructors. Instead, create one "master" constructor with all parameters, and have every other constructor delegate to it via this(args) with appropriate defaults. This way, if initialisation logic changes, you update only one place.
โญ Must-Know Interview Topics
Constructor vs method ยท default constructor disappears rule ยท constructor overloading ยท can constructor be private ยท can constructor be inherited ยท constructor return type trap ยท object creation sequence
Q1. What is a constructor? How is it different from a method?
A constructor is a special block used to initialise an object's state when it is created with new.

Key differences from a method:
1. Constructor has the same name as the class; methods can have any name
2. Constructor has no return type (not even void); methods must have one
3. Constructor is called automatically by the JVM on new; methods are called explicitly
4. Constructor cannot be called again after object creation; methods can be called any number of times
5. Constructor cannot be static, abstract, final, or synchronized
Q2. What is the default constructor? When does the compiler NOT provide it?
The default constructor is a no-argument constructor that the Java compiler automatically inserts if and only if the class defines no constructor at all. It calls super() and lets all instance variables take their type defaults.

The compiler does NOT provide it if you define any constructor โ€” even a parameterized one. This is the most common pitfall: adding a parameterized constructor silently removes the default constructor, breaking code that uses new ClassName() and frameworks that rely on no-arg instantiation.
Q3. Can a constructor have a return type?
No โ€” a constructor must have no return type at all. Not even void.

If you add any return type, Java treats it as a regular method (method naming is just a naming coincidence). The "constructor" with a return type will not be called on new โ€” it becomes an ordinary method that must be called explicitly. This is a classic exam/interview trick question.
Q4. Can a constructor be private? When would you use that?
Yes โ€” a constructor can be private. A private constructor prevents any class from creating an object using new ClassName() from outside the class itself.

This is used in two main patterns:
1. Singleton Pattern โ€” only one object of the class should ever exist. A private constructor ensures no external code creates a second one. A static factory method controls instance creation. (Covered in detail in Topic 11.)
2. Utility classes โ€” classes with only static methods (like Math, Collections) use a private constructor to prevent pointless instantiation.
Q5. Can constructors be inherited?
No โ€” constructors are not inherited. A subclass does not inherit the constructors of its parent class. However, a subclass constructor can call a parent constructor using super(args) as the first line.

If a subclass constructor does not explicitly call super(), the compiler automatically inserts a call to the parent's no-arg constructor. If the parent has no no-arg constructor (only parameterized), this causes a compile error in the subclass.
Q6. What is constructor overloading? How is the correct constructor chosen?
Constructor overloading means defining multiple constructors in a class with different parameter lists (different number, types, or order of parameters). Each constructor allows a different way to initialise an object.

The JVM resolves which constructor to call at compile time, based on the number and types of arguments provided in the new expression. This is static dispatch โ€” the same resolution mechanism as method overloading.
Q7. What is the complete sequence of steps when new MyClass() is called?
1. Class loading (if not already done) โ€” bytecode loaded, static blocks run, static vars initialised
2. Memory allocation โ€” JVM allocates space in Heap for the object; all instance variables set to defaults (0, false, null)
3. Instance initialisers run (if any โ€” e.g., int x = 5; directly in the class body)
4. Constructor executes โ€” super() is called first (walking up to Object), then the constructor body runs
5. Reference returned โ€” the address of the fully constructed object is assigned to the variable on the Stack

๐ŸŒฑ Constructors in Spring Boot

Constructors are central to Spring Boot โ€” the framework uses them extensively for dependency injection, and constructors on entity/DTO classes are critical for JPA and JSON serialisation.

โœ… Constructor patterns in Spring Boot
Constructor injection โ€” recommended way to inject dependencies; Spring calls the single constructor automatically
No-arg constructor for JPA โ€” Hibernate needs it to instantiate entities via reflection
All-args constructor โ€” used with Lombok @AllArgsConstructor for DTO creation
Lombok @RequiredArgsConstructor โ€” generates a constructor for all final fields โ€” Spring injection without boilerplate
OrderService.java + Order.java โ€” Spring Boot
// โ”€โ”€ Constructor injection (recommended over @Autowired field) โ”€โ”€
@Service
@RequiredArgsConstructor  // Lombok: generates constructor for final fields
public class OrderService {

    private final OrderRepository orderRepo;   // injected via constructor
    private final PaymentService  paymentSvc;  // injected via constructor

    // Lombok generates this automatically:
    // OrderService(OrderRepository orderRepo, PaymentService paymentSvc) {
    //     this.orderRepo   = orderRepo;
    //     this.paymentSvc  = paymentSvc;
    // }
}

// โ”€โ”€ JPA Entity โ€” must have no-arg constructor โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@Entity
@NoArgsConstructor             // Hibernate needs this
@AllArgsConstructor            // convenient for creating instances
public class Order {
    @Id @GeneratedValue
    private Long   id;
    private String product;
    private int    quantity;
}

// Usage:
// Order o = new Order(null, "Laptop", 2);  โ† @AllArgsConstructor
// Order o = new Order();                   โ† @NoArgsConstructor (Hibernate)
โš ๏ธ @Autowired on field vs constructor injection
Field injection (@Autowired private UserRepo repo;) is convenient but has problems: fields are null during unit tests unless you use a Spring context, you can't mark them final, and circular dependencies are harder to spot. Constructor injection solves all three: fields can be final, NullPointerExceptions are impossible (Spring must provide them before creating the bean), and circular dependencies fail fast at startup.
โญ Must-Know Exam Points
  • Constructor name = same as class name, no return type (not even void)
  • Default constructor = compiler-provided no-arg constructor, only if NO constructor is written
  • Defining any constructor removes the compiler-provided default constructor
  • Constructor overloading = multiple constructors with different parameter lists
  • Constructor cannot be static, abstract, final, or synchronized
  • Constructor cannot be inherited โ€” but can be called via super()
  • A method with same name as class and a return type is a regular method, not a constructor
  • Constructor can be private โ€” used for Singleton and utility classes

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
What is the return type of a constructor?No return typeNot void โ€” no return type at all. void makes it a method.
When does compiler add default constructor?Only if NO constructor definedOne parameterized constructor = compiler removes default
Can constructors be inherited?NoNot inherited โ€” only called via super()
Can constructor be private?YesUsed in Singleton pattern and utility classes
Can constructor be static?NoConstructors cannot be static, abstract, or final
void Demo() inside class Demo is aโ€ฆ?Regular methodHas a return type (void) โ†’ it's a method, not a constructor
Can a class have multiple constructors?Yes โ€” overloadingMust differ in parameter count/types, resolved at compile time

๐Ÿ“– One-Liner Definitions

  • Constructor โ€” Special class member with same name as class, no return type, auto-called on new to initialise object state.
  • Default constructor โ€” Compiler-generated no-arg constructor; only added when the class defines zero constructors.
  • Parameterized constructor โ€” Constructor that accepts arguments to set instance variables at object creation time.
  • Constructor overloading โ€” Multiple constructors in one class with different parameter lists, resolved at compile time.
  • Private constructor โ€” Constructor accessible only within the class; prevents external instantiation; used in Singleton and utility classes.
  • super() โ€” Calls the parent class constructor; implicitly inserted as first line if not written; must be first statement.
TOPIC 10

Copy Constructor

๐Ÿ“‹ What is a Copy Constructor?

A copy constructor is a constructor that creates a new object as a duplicate of an existing object of the same class. It takes a reference to an object of its own class as the only parameter and uses it to initialise the new object's fields.

๐Ÿ“Œ Java vs C++
In C++, the copy constructor is a language-level feature โ€” the compiler generates one automatically and calls it implicitly in many situations (pass by value, return by value, assignment).

In Java, there is no built-in copy constructor. Java has no pass-by-value for objects and no implicit copy construction. If you want a copy constructor, you must write it yourself. Java's alternative is the clone() method (from Object), but copy constructors are generally preferred as they are simpler, more readable, and don't require implementing Cloneable.

๐Ÿ” The Core Problem โ€” Reference Copy Trap

When you write Student s2 = s1;, you do not create a new object. Both s1 and s2 point to the same Heap object. Changing s2's fields changes s1's fields too โ€” they are aliases, not independent copies.

REFERENCE COPY vs OBJECT COPY โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โŒ Reference copy (assignment) โ€” NOT a new object: Student s2 = s1; STACK HEAP s1 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ [ name="Rahul", age=20 ] s2 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ–ฒ same object! s2.name = "Priya" โ†’ s1.name is also "Priya"! โœ… Copy constructor โ€” new independent object: Student s2 = new Student(s1); STACK HEAP s1 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ [ name="Rahul", age=20 ] object A s2 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ [ name="Rahul", age=20 ] object B (copy) s2.name = "Priya" โ†’ s1.name stays "Rahul" โœ“ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

๐Ÿงช Shallow Copy vs Deep Copy

This distinction is critical when the class contains fields that are themselves objects (reference types).

SHALLOW COPY vs DEEP COPY โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Student { String name; int[] scores; // โ† mutable reference type field! } SHALLOW COPY โ€” copies reference, shares the array: โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ s1.name โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ "Rahul" (String)โ”‚ (ok โ€” String is immutable) โ”‚ s1.scores โ”‚โ”€โ”€โ” โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ””โ”€โ”€โ”€โ–บโ”‚ [90, 85, 92] โ”‚ โ† SHARED array! โ”‚ s2.name โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ "Rahul" (copy) โ”‚ โ”‚ s2.scores โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ same array ref! โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ s2.scores[0] = 99 โ†’ s1.scores[0] is also 99! โŒ DEEP COPY โ€” copies the array too: s2.scores = Arrays.copyOf(s1.scores, s1.scores.length); โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ s1.scores โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ [90, 85, 92] โ”‚ โ† original โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ s2.scores โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ [90, 85, 92] โ”‚ โ† independent copy โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ s2.scores[0] = 99 โ†’ s1.scores[0] stays 90 โœ“ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
โš ๏ธ String fields are safe in shallow copy โ€” arrays and objects are not
String is immutable in Java โ€” once created, its value never changes. So sharing a String reference between two objects is safe: if you "change" a String field, you're actually pointing the reference to a new String object, not modifying the shared one. But mutable fields like int[], ArrayList, or other custom objects MUST be deep-copied; otherwise both objects share and can mutate the same data.

๐Ÿ“ When to Use a Copy Constructor

  • When you need an independent duplicate of an object to modify without affecting the original
  • When implementing immutable snapshots โ€” take a copy before passing to untrusted code
  • As an alternative to clone() โ€” clearer, no checked exception, no need for Cloneable
  • In defensive copying โ€” return copies from getters to prevent external mutation of internal state

๐Ÿ’ป Program 1 โ€” Basic Copy Constructor

CopyConstructorBasic.java
public class Student {
    String name;
    int    age;

    // Regular parameterized constructor
    Student(String name, int age) {
        this.name = name;
        this.age  = age;
    }

    // Copy constructor โ€” takes a Student, copies its fields
    Student(Student other) {
        this.name = other.name;  // String is immutable โ€” safe
        this.age  = other.age;   // primitive โ€” direct copy
    }

    void display(String label) {
        System.out.printf("%s โ†’ name=%-8s age=%d%n", label, name, age);
    }

    public static void main(String[] args) {
        Student s1 = new Student("Rahul", 20);

        // โŒ Reference copy โ€” same object
        Student ref = s1;

        // โœ… Copy constructor โ€” new independent object
        Student s2 = new Student(s1);

        // Modify s2 and ref
        s2.name  = "Priya";
        s2.age   = 22;
        ref.name = "Changed!";  // also changes s1

        s1.display("s1 ");   // affected by ref change
        s2.display("s2 ");   // independent โ€” unaffected
        ref.display("ref");  // same as s1
    }
}
โ–ถ OUTPUT
s1 โ†’ name=Changed! age=20 s2 โ†’ name=Priya age=22 ref โ†’ name=Changed! age=20

๐Ÿ’ป Program 2 โ€” Shallow vs Deep Copy (Array Field)

ShallowVsDeep.java
import java.util.Arrays;

public class Gradebook {
    String  student;
    int[]   scores;   // mutable reference type โ€” needs deep copy

    Gradebook(String student, int[] scores) {
        this.student = student;
        this.scores  = scores;
    }

    // SHALLOW copy constructor โ€” copies the array REFERENCE
    Gradebook(Gradebook other, boolean deep) {
        this.student = other.student;
        if (deep) {
            // DEEP copy โ€” new array with same values
            this.scores = Arrays.copyOf(other.scores, other.scores.length);
        } else {
            // SHALLOW copy โ€” copies the reference, shares array
            this.scores = other.scores;
        }
    }

    void display(String label) {
        System.out.println(label + " " + student
            + ": " + Arrays.toString(scores));
    }

    public static void main(String[] args) {
        Gradebook original = new Gradebook("Rahul", new int[]{90,85,92});

        Gradebook shallow = new Gradebook(original, false);
        Gradebook deep    = new Gradebook(original, true);

        // Modify via shallow copy
        shallow.scores[0] = 99;

        System.out.println("After shallow.scores[0] = 99:");
        original.display("original:");  // AFFECTED โ€” shared array!
        shallow.display("shallow: ");

        // Modify via deep copy
        deep.scores[0] = 55;
        System.out.println("After deep.scores[0] = 55:");
        original.display("original:");  // UNAFFECTED โ€” independent array
        deep.display("deep:    ");
    }
}
โ–ถ OUTPUT
After shallow.scores[0] = 99: original: Rahul: [99, 85, 92] shallow: Rahul: [99, 85, 92] After deep.scores[0] = 55: original: Rahul: [99, 85, 92] deep: Rahul: [55, 85, 92]

๐Ÿ’ป Program 3 โ€” Copy Constructor vs clone()

CopyVsClone.java
public class Point {
    int x, y;

    Point(int x, int y) { this.x=x; this.y=y; }

    // โœ… Copy constructor โ€” clean, no exceptions, no Cloneable
    Point(Point other) {
        this.x = other.x;
        this.y = other.y;
    }

    public static void main(String[] args) {
        Point p1 = new Point(3, 4);
        Point p2 = new Point(p1);  // copy constructor

        p2.x = 10;
        System.out.println("p1: ("+p1.x+","+p1.y+")");  // unchanged
        System.out.println("p2: ("+p2.x+","+p2.y+")");  // independent
        System.out.println("Same object? " + (p1 == p2));    // false
    }
}
โ–ถ OUTPUT
p1: (3,4) p2: (10,4) Same object? false
๐Ÿ’ก Copy Constructor vs clone() โ€” When to use which
Prefer copy constructor in almost all cases: it's explicit, type-safe, no checked exception, no need to implement Cloneable, works well with inheritance, and is easy to add deep-copy logic. Use clone() only when required by a legacy API or when working with arrays (arrays have a built-in .clone() that does a shallow copy).
โญ Must-Know Interview Topics
Reference copy vs copy constructor ยท shallow vs deep copy ยท copy constructor vs clone() ยท when String is safe in shallow copy ยท immutable objects and copying ยท defensive copying
Q1. What is a copy constructor? Does Java provide one automatically?
A copy constructor is a constructor that takes an object of the same class as its parameter and creates a new object with the same field values.

No โ€” Java does NOT provide a copy constructor automatically. Unlike C++, Java has no language-level copy constructor. You must write it manually if needed. Java's alternative is Object.clone(), but copy constructors are generally preferred.
Q2. What is the difference between a reference copy and a copy constructor?
Reference copy (Student s2 = s1): No new object is created. Both s1 and s2 point to the same Heap object. Any change through either variable affects the same object.

Copy constructor (Student s2 = new Student(s1)): A new Heap object is created with field values copied from s1. s1 and s2 are independent โ€” modifying one does not affect the other (assuming proper deep copy for reference fields).
Q3. What is the difference between shallow copy and deep copy?
Shallow copy: Copies all field values directly. Primitive fields are safely duplicated. But reference-type fields (arrays, objects) have only their references copied โ€” both original and copy point to the same nested object. Mutating the nested object through one affects the other.

Deep copy: Recursively copies all reference-type fields too โ€” creates new independent instances of every nested object. Both original and copy are completely independent at all levels.
Q4. Why are String fields safe in a shallow copy?
Because String is immutable in Java โ€” its value can never change after creation. When you "assign" a new value to a String field, you are actually creating a new String object and pointing the reference to it. The shared original String is untouched.

So even if both original and copy share the same String reference, neither can corrupt the other's data. This is why shallow copy is safe for classes containing only primitives and Strings โ€” but unsafe the moment any mutable reference type (array, ArrayList, custom object) is involved.
Q5. Copy constructor vs clone() โ€” what are the advantages of copy constructor?
Copy constructor advantages over clone():
1. No interface required โ€” clone() requires implementing Cloneable; forgetting it throws CloneNotSupportedException
2. No checked exception โ€” clone() throws CloneNotSupportedException; copy constructor throws nothing
3. Type safe โ€” returns the correct type directly; clone() returns Object and needs casting
4. Explicit and readable โ€” clear what fields are copied and how
5. Deep copy is straightforward โ€” just initialise fields manually; clone()'s deep copy is tricky with inheritance
Q6. What is defensive copying? Where is it used?
Defensive copying means creating a copy of a mutable object before storing it or returning it, so external code cannot corrupt the internal state of a class.

Two places:
1. In constructors: if a constructor receives a mutable object (e.g., int[] or Date), copy it before storing: this.scores = Arrays.copyOf(scores, scores.length) โ€” otherwise the caller could modify the original array and affect your object.
2. In getters: instead of returning the actual internal array, return a copy: return Arrays.copyOf(this.scores, this.scores.length). This is key to building truly encapsulated or immutable classes.
Q7. Tricky: Is new Student(s1) guaranteed to give a true deep copy?
No โ€” only if you implement it correctly. Writing a copy constructor does not automatically make it a deep copy. Whether it is shallow or deep depends entirely on how you write it.

If your copy constructor does this.scores = other.scores for an array field, that is shallow โ€” shared array. If it does this.scores = Arrays.copyOf(other.scores, other.scores.length), that is deep for that level. For nested custom objects, you must recursively copy each one. The developer is fully responsible for the depth of copying.

๐ŸŒฑ Copy Constructors & Defensive Copying in Spring Boot

In Spring Boot, copy constructors and defensive copying appear in two key areas: DTO mapping (converting between Entity and Data Transfer Object) and immutable value objects to prevent shared mutable state between service layers.

โœ… Where copying matters in Spring Boot
Entity โ†’ DTO conversion โ€” copy fields from JPA entity to a DTO to avoid exposing internal entity structure in API responses
DTO โ†’ Entity โ€” copy fields from incoming request DTO into a managed entity before saving
Defensive copy in @Value objects โ€” prevent callers from mutating internal state of domain objects
ModelMapper / MapStruct โ€” library-level copy constructors that auto-map fields by name
UserDTO.java โ€” Spring Boot Entity to DTO copy
// JPA Entity โ€” internal, has DB-specific fields
@Entity
public class User {
    @Id private Long   id;
    private String name;
    private String email;
    private String passwordHash;  // must NOT expose in API!
}

// DTO โ€” safe to expose, no sensitive fields
public class UserDTO {
    private Long   id;
    private String name;
    private String email;

    // "Copy constructor" from Entity โ†’ DTO
    public UserDTO(User user) {
        this.id    = user.getId();
        this.name  = user.getName();
        this.email = user.getEmail();
        // passwordHash intentionally excluded
    }
}

// In service layer:
// User entity = repo.findById(id);
// return new UserDTO(entity);  โ† safe copy for API response
๐Ÿ“Œ Lombok @Value โ€” Immutable DTO with forced defensive copy
Lombok's @Value annotation creates an immutable class (all fields private final, no setters, all-args constructor). Combined with defensive copying in the constructor, it guarantees that the DTO's state can never change after creation โ€” ideal for read-only API responses and thread-safe value objects.
โญ Must-Know Exam Points
  • s2 = s1 is a reference copy โ€” no new object, same Heap address
  • Copy constructor signature: ClassName(ClassName other)
  • Java does NOT provide a copy constructor automatically (unlike C++)
  • Shallow copy: primitives duplicated, reference fields shared
  • Deep copy: all fields fully independent including nested objects/arrays
  • String is safe in shallow copy โ€” immutable, cannot be mutated
  • Arrays and mutable objects need deep copy โ€” use Arrays.copyOf()
  • Copy constructor preferred over clone() โ€” no Cloneable, no checked exception

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Does Java auto-provide a copy constructor?NoC++ does; Java does not โ€” must write manually
Student s2 = s1; creates a new object?NoReference copy only โ€” same Heap object
Is String safe in a shallow copy?YesString is immutable โ€” "changing" creates a new object
Is int[] safe in a shallow copy?NoArray is mutable โ€” changes affect both objects
Does new Student(s1) guarantee deep copy?NoDepends on how you wrote the copy constructor
Advantage of copy constructor over clone()?No checked exception, no Cloneable, type-safeclone() requires implementing Cloneable and throws CloneNotSupportedException

๐Ÿ“– One-Liner Definitions

  • Copy constructor โ€” A constructor that takes an object of the same class and creates a new independent duplicate.
  • Reference copy โ€” Assigning one object variable to another; both point to the same Heap object โ€” not a new object.
  • Shallow copy โ€” A copy where primitives are duplicated but reference-type fields still point to the same nested objects.
  • Deep copy โ€” A copy where all fields including nested objects and arrays are fully duplicated and independent.
  • Defensive copy โ€” Creating a copy of a mutable object when storing it or returning it, to protect internal state from external mutation.
  • Immutable class โ€” A class whose state cannot change after construction; String is the most common example in Java.
TOPIC 11

Private Constructor & Singleton Pattern

๐Ÿ”’ Private Constructor

Marking a constructor private means no code outside the class can call new ClassName(). This gives the class complete control over how and when its instances are created. It is the foundation of two important patterns: the Singleton and utility classes.

WHY PRIVATE CONSTRUCTOR? โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Normal class (public constructor): new Database() โ† anyone can create unlimited instances new Database() new Database() โ† 3 separate DB connection objects โ€” wasteful! Private constructor: new Database() โ† compile error from outside the class! Database.getInstance() โ† controlled access via static method โ† returns the ONE shared instance โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

๐Ÿ›๏ธ The Singleton Design Pattern

The Singleton is a creational design pattern that ensures a class has exactly one instance throughout the lifetime of the application, and provides a global access point to that instance.

Real-world analogies: a country has one government, an OS has one file system, an app has one configuration manager, a DB pool has one connection pool.

๐Ÿ“Œ Three ingredients of a Singleton
1. Private constructor โ€” prevents new from outside
2. Private static instance field โ€” holds the one and only object
3. Public static getInstance() method โ€” creates the instance on first call, returns it on all subsequent calls

๐Ÿ“ Singleton Variants

Variant 1 โ€” Eager Initialisation

The instance is created when the class loads โ€” before anyone calls getInstance(). Simple and thread-safe (class loading is thread-safe in the JVM), but wastes resources if the singleton is never used.

EAGER SINGLETON โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Config { private static final Config INSTANCE = new Config(); // created at class load private Config() {} public static Config getInstance() { return INSTANCE; } } Class loads โ†’ INSTANCE created immediately getInstance() โ†’ always returns the same INSTANCE Thread-safe? YES โ€” class loading is inherently thread-safe

Variant 2 โ€” Lazy Initialisation

The instance is created only on the first call to getInstance(). Saves resources if the singleton might never be needed, but the basic version is not thread-safe.

LAZY SINGLETON (not thread-safe) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Config { private static Config instance; // null until first call private Config() {} public static Config getInstance() { if (instance == null) // Thread A and Thread B instance = new Config(); // could BOTH pass this check! return instance; } } Problem: two threads can both see instance==null simultaneously โ†’ both create a new Config() โ†’ TWO instances exist โ†’ broken!

Variant 3 โ€” Thread-Safe Lazy (Double-Checked Locking)

The production-ready lazy singleton. Uses synchronized and volatile to guarantee exactly one instance even under concurrent access.

DOUBLE-CHECKED LOCKING SINGLETON โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Config { private static volatile Config instance; private Config() {} public static Config getInstance() { if (instance == null) { // 1st check (no lock) synchronized (Config.class) { if (instance == null) { // 2nd check (with lock) instance = new Config(); } } } return instance; } } volatile โ†’ prevents CPU instruction reordering 1st check โ†’ fast path: avoids locking after first creation 2nd check โ†’ ensures only one thread creates the instance synchronized โ†’ mutual exclusion during creation

Variant 4 โ€” Bill Pugh (Best Practice)

The cleanest, most elegant solution. Uses a static inner helper class. The inner class is not loaded until getInstance() is first called (lazy), and class loading is thread-safe (no synchronization needed).

BILL PUGH SINGLETON (recommended) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Config { private Config() {} private static class Holder { static final Config INSTANCE = new Config(); } public static Config getInstance() { return Holder.INSTANCE; } } Why it works: โ†’ Holder class is NOT loaded when Config class loads โ†’ Holder is loaded on FIRST call to getInstance() โ†’ JVM class loading is thread-safe โ†’ no synchronized needed โ†’ Lazy + Thread-safe + No volatile + No synchronized = PERFECT

๐Ÿšซ Singleton Killers โ€” What Can Break It

ThreatHow It Breaks SingletonDefence
ReflectionConstructor.setAccessible(true) bypasses privateThrow exception in constructor if instance already exists
SerialisationDeserialising creates a new objectImplement readResolve() returning INSTANCE
Cloningclone() creates a new objectOverride clone() to throw CloneNotSupportedException
Multiple ClassLoadersEach classloader has its own class โ€” separate instancesUse enum singleton (immune to all three above)

Variant 5 โ€” Enum Singleton (Most Robust)

ENUM SINGLETON โ€” immune to reflection, serialisation, cloning โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ enum Config { INSTANCE; public void doSomething() { ... } } Usage: Config.INSTANCE.doSomething(); Benefits: โ†’ JVM guarantees exactly ONE enum constant โ€” ever โ†’ Immune to reflection attacks โ†’ Serialisation-safe by default โ†’ Thread-safe by design Downside: cannot extend another class (enums can't extend)

๐Ÿ’ป Program 1 โ€” Eager Singleton: Proving One Instance

EagerSingleton.java
class DatabaseConnection {

    // 1. Private static instance โ€” created at class load time
    private static final DatabaseConnection INSTANCE
        = new DatabaseConnection();

    // 2. Private constructor โ€” no one outside can call new
    private DatabaseConnection() {
        System.out.println("DB connection created (once!)");
    }

    // 3. Public static access point
    public static DatabaseConnection getInstance() {
        return INSTANCE;
    }

    public void query(String sql) {
        System.out.println("Executing: " + sql);
    }
}

public class EagerSingleton {
    public static void main(String[] args) {
        DatabaseConnection d1 = DatabaseConnection.getInstance();
        DatabaseConnection d2 = DatabaseConnection.getInstance();
        DatabaseConnection d3 = DatabaseConnection.getInstance();

        // All three point to the same object
        System.out.println("d1 == d2: " + (d1 == d2));
        System.out.println("d2 == d3: " + (d2 == d3));
        System.out.println("hashCode d1: " + d1.hashCode());
        System.out.println("hashCode d2: " + d2.hashCode());

        d1.query("SELECT * FROM users");
    }
}
โ–ถ OUTPUT
DB connection created (once!) d1 == d2: true d2 == d3: true hashCode d1: 1163157884 hashCode d2: 1163157884 Executing: SELECT * FROM users

๐Ÿ’ป Program 2 โ€” Bill Pugh Singleton (Production Best Practice)

BillPughSingleton.java
class AppConfig {

    private String dbUrl;
    private int    maxConnections;

    // Private constructor โ€” loads config
    private AppConfig() {
        // Simulate loading from properties file
        this.dbUrl          = "jdbc:mysql://localhost:3306/mydb";
        this.maxConnections = 10;
        System.out.println("AppConfig loaded");
    }

    // Inner Holder class โ€” loaded ONLY when getInstance() is first called
    private static class Holder {
        static final AppConfig INSTANCE = new AppConfig();
    }

    public static AppConfig getInstance() {
        return Holder.INSTANCE;  // triggers Holder class load on first call
    }

    public void printConfig() {
        System.out.println("DB: " + dbUrl + " | MaxConn: " + maxConnections);
    }
}

public class BillPughSingleton {
    public static void main(String[] args) {
        System.out.println("Before getInstance()");
        AppConfig cfg1 = AppConfig.getInstance();  // triggers creation
        AppConfig cfg2 = AppConfig.getInstance();  // returns same
        cfg1.printConfig();
        System.out.println("Same instance: " + (cfg1 == cfg2));
    }
}
โ–ถ OUTPUT
Before getInstance() AppConfig loaded DB: jdbc:mysql://localhost:3306/mydb | MaxConn: 10 Same instance: true

๐Ÿ’ป Program 3 โ€” Utility Class with Private Constructor

MathUtils.java
public class MathUtils {

    // Private constructor โ€” prevents instantiation of utility class
    private MathUtils() {
        throw new UnsupportedOperationException(
            "MathUtils is a utility class โ€” do not instantiate!");
    }

    public static int factorial(int n) {
        if (n <= 1) return 1;
        return n * factorial(n - 1);
    }

    public static boolean isPrime(int n) {
        if (n < 2) return false;
        for (int i = 2; i * i <= n; i++)
            if (n % i == 0) return false;
        return true;
    }

    public static void main(String[] args) {
        // Use directly via class name โ€” no object needed
        System.out.println("5! = "          + MathUtils.factorial(5));
        System.out.println("17 prime? "     + MathUtils.isPrime(17));
        System.out.println("18 prime? "     + MathUtils.isPrime(18));
        // new MathUtils();  โ† compile-time accessible but throws at runtime
    }
}
โ–ถ OUTPUT
5! = 120 17 prime? true 18 prime? false
โš ๏ธ Basic lazy singleton is NOT thread-safe
Never use the simple lazy singleton (if (instance == null) instance = new X()) in multi-threaded code. Two threads can both see null simultaneously and each create an instance. Use the Bill Pugh idiom or eager initialisation for production code. Double-checked locking also works but requires both synchronized and volatile to be correct.
๐Ÿ’ก Quick guide โ€” which Singleton variant to use?
Eager โ€” fine when the object is always needed and lightweight to create.
Bill Pugh โ€” best default choice: lazy, thread-safe, no overhead.
Enum โ€” use when you also need protection against reflection/serialisation attacks.
Double-checked locking โ€” valid but verbose; prefer Bill Pugh instead.
โญ Must-Know Interview Topics
Singleton definition ยท 3 ingredients ยท eager vs lazy ยท thread-safety of each variant ยท Bill Pugh ยท Singleton killers (reflection/serialisation) ยท enum singleton ยท Spring singleton scope
Q1. What is the Singleton pattern? What are its three components?
The Singleton pattern ensures a class has exactly one instance and provides a global access point to it.

Three components:
1. Private constructor โ€” prevents new ClassName() from outside
2. Private static field of the same type โ€” holds the single instance
3. Public static getInstance() โ€” creates on first call, returns the same instance on all subsequent calls
Q2. What is the difference between eager and lazy singleton?
Eager: Instance created when the class is loaded โ€” before getInstance() is ever called. Simple and inherently thread-safe (JVM class loading is synchronised). Downside: creates the object even if it's never used.

Lazy: Instance created only on the first call to getInstance(). Saves resources if the singleton might never be needed. Downside: basic lazy implementation is not thread-safe โ€” needs extra care (volatile + synchronized or Bill Pugh idiom).
Q3. How do you make a Singleton thread-safe?
Three approaches:
1. Eager initialisation โ€” thread-safe by default (class loading is atomic)
2. Bill Pugh (static inner class) โ€” lazy + thread-safe without any synchronisation overhead. Best choice for most cases.
3. Double-checked locking โ€” use volatile on the field and synchronized on the creation block. The volatile keyword is mandatory โ€” without it, a partially-constructed object can be visible to another thread due to instruction reordering.
Q4. How can Singleton be broken? How do you protect against it?
Reflection: Constructor.setAccessible(true) can bypass the private constructor and create a second instance. Defence: throw an exception in the constructor if an instance already exists.

Serialisation: Deserialising a singleton creates a new object. Defence: implement readResolve() returning INSTANCE.

Cloning: clone() creates a new object. Defence: override clone() to throw CloneNotSupportedException.

Enum singleton is the only approach immune to all three โ€” the JVM guarantees enum constants are never duplicated, even via reflection or serialisation.
Q5. Why is the enum singleton considered the best Singleton implementation?
The enum singleton (enum Config { INSTANCE; }) is immune to all Singleton killers:
โ€” Reflection cannot create a second enum constant (JVM enforces this)
โ€” Serialisation is handled correctly by default for enums
โ€” Cloning is not allowed for enums
โ€” Thread-safe by the JVM
โ€” Lazily initialised (enum class loaded only when first referenced)

Downside: enums cannot extend another class (Java's single-inheritance constraint), so if the singleton needs to extend a base class, you must use another variant.
Q6. Is Spring's @Service/@Component a Singleton?
Yes โ€” Spring beans are Singleton by default, but it is Spring's container-managed singleton, not a class-level Java Singleton.

Difference: a Java Singleton uses a private constructor to guarantee one instance per JVM. Spring's singleton scope means one instance per Spring ApplicationContext โ€” the class itself can still be instantiated with new. If you create a second ApplicationContext, you get a second bean instance. Spring's singleton is a container-level concept; Java's Singleton is a language/class-level concept.
Q7. When should you NOT use Singleton?
Avoid Singleton when:
1. Unit testing: Singletons carry state between tests, making tests order-dependent and brittle. They cannot easily be mocked or replaced with a test double.
2. High concurrency: If the singleton holds mutable state, every thread contending on it creates a bottleneck.
3. Multiple independent contexts: If you need separate instances for different users, tenants, or subsystems, Singleton is the wrong pattern.

Prefer dependency injection (Spring's IoC container) over manual Singletons in production code โ€” it gives you singleton behaviour with testability.

๐ŸŒฑ Singleton in Spring Boot

Spring Boot is built around the Singleton concept. Every @Component, @Service, @Repository, and @Controller bean is a singleton by default within the ApplicationContext โ€” Spring manages the instance and injects it wherever needed.

โœ… Spring Singleton vs Java Singleton
Java Singleton โ€” enforced at the class level via private constructor; one instance per JVM
Spring Singleton scope โ€” one instance per ApplicationContext; the class still has a public constructor (Spring uses it to create the one bean); multiple ApplicationContexts = multiple instances
AppConfig.java + ConfigService.java โ€” Spring Boot
// Spring manages ONE instance of this bean (singleton scope default)
@Service
public class ConfigService {
    private final String dbUrl;

    public ConfigService(@Value("${spring.datasource.url}") String dbUrl) {
        this.dbUrl = dbUrl;
        System.out.println("ConfigService created once by Spring");
    }
    public String getDbUrl() { return dbUrl; }
}

// Manual Java Singleton โ€” needed when outside Spring context
// e.g. in a non-Spring utility or library code
public class LegacyConfig {
    private static class Holder {
        static final LegacyConfig INSTANCE = new LegacyConfig();
    }
    private LegacyConfig() {}
    public static LegacyConfig getInstance() { return Holder.INSTANCE; }
}
โš ๏ธ Never store mutable request state in a Spring singleton bean
Because all requests share the same singleton bean instance, storing per-request data as instance variables creates race conditions. User A's data can leak to User B. Always keep Spring singleton beans stateless โ€” all mutable state should live in local method variables, the database, or a request-scoped bean.
โญ Must-Know Exam Points
  • Singleton = one instance per application + global access point
  • Three components: private constructor + private static field + public static getInstance()
  • Eager singleton โ€” created at class load time โ€” thread-safe, no lazy
  • Lazy singleton (basic) โ€” NOT thread-safe without extra measures
  • Bill Pugh โ€” best default: lazy + thread-safe via static inner class
  • Enum singleton โ€” immune to reflection, serialisation, cloning
  • Double-checked locking needs both volatile AND synchronized
  • Spring beans are singleton by default โ€” but it is container-managed, not class-level
  • Private constructor also used in utility classes (all static methods)

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Is eager singleton thread-safe?YesJVM class loading is atomic โ€” instance created safely
Is basic lazy singleton thread-safe?NoTwo threads can both see null and create separate instances
Can reflection break an eager singleton?YessetAccessible(true) bypasses private โ€” must guard in constructor
Which singleton variant is immune to reflection?Enum singletonJVM prevents creating new enum instances via reflection
Is Spring @Service singleton the same as Java Singleton?NoSpring = one per ApplicationContext; Java = one per JVM via private constructor
Why volatile in double-checked locking?Prevents instruction reorderingWithout volatile, partially-constructed object may be visible to other threads

๐Ÿ“– One-Liner Definitions

  • Singleton pattern โ€” Design pattern ensuring a class has exactly one instance with a global access point.
  • Eager singleton โ€” Instance created at class load time; thread-safe but not lazy.
  • Lazy singleton โ€” Instance created on first getInstance() call; saves resources but needs thread-safety measures.
  • Bill Pugh singleton โ€” Uses a static inner holder class for lazy, thread-safe singleton without synchronisation overhead.
  • Enum singleton โ€” Singleton implemented as an enum constant; immune to reflection, serialisation, and cloning.
  • Double-checked locking โ€” Lazy singleton using two null checks and synchronized + volatile to safely create one instance in multithreaded code.
  • Utility class โ€” Class with only static methods and a private constructor to prevent instantiation (e.g., Math, Collections).
TOPIC 12

Naming Conventions

๐Ÿ“ Why Naming Conventions Matter

Java naming conventions are not enforced by the compiler โ€” your code compiles fine if you break them. But they are universally followed in the Java ecosystem because they make code instantly readable to any Java developer, enable IDE tooling to work better, and are required for frameworks like Spring and Hibernate that rely on naming patterns via reflection.

๐Ÿ“Œ Convention vs Rule
A rule is enforced by the compiler โ€” violating it is a compile error.
A convention is a community agreement โ€” violating it compiles fine but makes you a bad teammate. Java's naming conventions come from Sun Microsystems' original Java Code Conventions document and are followed by virtually all Java teams worldwide.

๐Ÿ”ก The Four Casing Styles

JAVA CASING STYLES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ PascalCase (UpperCamelCase) โ†’ Every word starts with uppercase, no separators โ†’ StudentGrade HttpRequest ArrayList OrderService camelCase (lowerCamelCase) โ†’ First word lowercase, subsequent words capitalised โ†’ studentGrade maxRetryCount getUserById firstName ALL_CAPS (SCREAMING_SNAKE_CASE) โ†’ All uppercase, words separated by underscores โ†’ MAX_SIZE PI DEFAULT_TIMEOUT HTTP_STATUS_OK lowercase (package names only) โ†’ All lowercase, dots as separators (no underscores) โ†’ com.example.service java.util org.springframework โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

๐Ÿ“‹ Complete Naming Rules by Identifier Type

IdentifierConventionExamples
ClassPascalCase โ€” noun or noun phraseStudent, OrderService, HttpClient
InterfacePascalCase โ€” adjective or nounRunnable, Serializable, Comparable
MethodcamelCase โ€” verb or verb phrasegetName(), calculateTotal(), isValid()
Variable (instance/local)camelCase โ€” nounfirstName, totalAmount, studentList
ParametercamelCase โ€” same as variableuserId, orderDate, maxRetries
Constant (static final)ALL_CAPS with underscoresMAX_SIZE, PI, DEFAULT_TIMEOUT
Packageall lowercase, no underscorescom.example.service, java.util
Enum typePascalCase (like a class)DayOfWeek, OrderStatus
Enum constantALL_CAPSMONDAY, PENDING, HTTP_OK
Generic type parameterSingle uppercase letterT, E, K, V, N
AnnotationPascalCase@Override, @NotNull, @SpringBootTest

๐Ÿ”ค Specific Naming Patterns

METHOD NAMING PATTERNS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Getters: get + FieldName โ†’ getName(), getAge() Boolean: is/has/can + ... โ†’ isActive(), hasPermission() Setters: set + FieldName โ†’ setName(), setAge() Actions: verb phrase โ†’ calculateTax(), sendEmail() Factory: create/of/from/... โ†’ createOrder(), of(value) Converters: to + Type โ†’ toString(), toList() โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ CLASS / INTERFACE NAMING PATTERNS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Service classes: OrderService, UserService Repository/DAO: OrderRepository, UserDAO Controller: OrderController, UserController Exception classes: OrderNotFoundException, InvalidAgeException Utility classes: StringUtils, DateUtils, MathUtils Interface (ability): Runnable, Comparable, Serializable Interface (contract): UserService, PaymentGateway Abstract base: AbstractVehicle, BaseEntity Implementation: OrderServiceImpl (when interface exists) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

โŒ Common Naming Mistakes

BAD vs GOOD NAMING โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โŒ class student โœ… class Student โŒ class STUDENT โœ… class Student โŒ int First_Name โœ… int firstName โŒ void Get_name() โœ… void getName() โŒ final int maxsize = 5 โœ… final int MAX_SIZE = 5 โŒ package Com.Example โœ… package com.example โŒ String S โœ… String studentName (be descriptive) โŒ int data โœ… int studentAge (be specific) โŒ void doStuff() โœ… void calculateMonthlyTax() โŒ class Irunnable โœ… interface Runnable (no I prefix) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Note: Java does NOT use the Hungarian notation (no I prefix for interfaces, no m_ prefix for members) unlike C# or C++
๐Ÿ’ก Length guideline
Variable names should be long enough to be meaningful, short enough to read easily. Single-letter names are acceptable only for: loop counters (i, j, k), generic type parameters (T, E), and lambda parameters in very short expressions. Everything else should be descriptive: totalOrderAmount is better than t, tot, or total.

๐Ÿ’ป Program 1 โ€” Conventions Applied: Well-Named Class

BankAccount.java โ€” all conventions applied
package com.example.banking;   // lowercase package

public class BankAccount {          // PascalCase class

    // ALL_CAPS constants
    public static final double MIN_BALANCE     = 500.0;
    public static final int    MAX_TRANSACTIONS = 100;

    // camelCase instance variables
    private String accountNumber;
    private String holderName;
    private double currentBalance;
    private boolean isActive;

    // PascalCase constructor name (matches class)
    public BankAccount(String accountNumber, String holderName) {
        this.accountNumber  = accountNumber;
        this.holderName      = holderName;
        this.currentBalance  = MIN_BALANCE;
        this.isActive        = true;
    }

    // camelCase methods โ€” verb phrases
    public void depositAmount(double amount) {      // camelCase param
        if (amount > 0) currentBalance += amount;
    }

    public boolean withdrawAmount(double amount) {
        if (currentBalance - amount >= MIN_BALANCE) {
            currentBalance -= amount;
            return true;
        }
        return false;
    }

    // Boolean getter uses "is" prefix
    public boolean isActive() { return isActive; }

    // Standard getters โ€” get + FieldName (PascalCase field name)
    public String  getAccountNumber() { return accountNumber; }
    public String  getHolderName()    { return holderName; }
    public double  getCurrentBalance() { return currentBalance; }

    public static void main(String[] args) {
        BankAccount account = new BankAccount("ACC001", "Rahul");
        account.depositAmount(2000);
        boolean success = account.withdrawAmount(1000);

        System.out.println("Holder  : " + account.getHolderName());
        System.out.println("Balance : " + account.getCurrentBalance());
        System.out.println("Active  : " + account.isActive());
        System.out.println("Withdraw: " + success);
        System.out.println("MIN_BAL : " + BankAccount.MIN_BALANCE);
    }
}
โ–ถ OUTPUT
Holder : Rahul Balance : 1500.0 Active : true Withdraw: true MIN_BAL : 500.0

๐Ÿ’ป Program 2 โ€” Enum Naming + Generic Type Parameter

NamingShowcase.java
// Enum type โ†’ PascalCase | Enum constants โ†’ ALL_CAPS
enum OrderStatus {
    PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED
}

// Generic class โ†’ single uppercase letter T for type parameter
class ApiResponse<T> {
    private T       data;
    private String  message;
    private boolean success;

    ApiResponse(T data, String message, boolean success) {
        this.data    = data;
        this.message = message;
        this.success = success;
    }
    public boolean isSuccess() { return success; }  // boolean โ†’ is prefix
    public      T getData()    { return data; }
    public String getMessage() { return message; }
}

public class NamingShowcase {
    public static void main(String[] args) {
        OrderStatus currentStatus = OrderStatus.CONFIRMED;
        System.out.println("Order status: " + currentStatus);

        ApiResponse<String> response =
            new ApiResponse<>("user-123", "User found", true);
        System.out.println("Success : " + response.isSuccess());
        System.out.println("Data    : " + response.getData());
        System.out.println("Message : " + response.getMessage());
    }
}
โ–ถ OUTPUT
Order status: CONFIRMED Success : true Data : user-123 Message : User found
โš ๏ธ Frameworks depend on naming conventions
Spring uses getter/setter naming to auto-map JSON fields (Jackson). Hibernate uses field names to map to DB columns. Lombok generates getters/setters based on field names. If you name a boolean field active, Spring/Jackson expect isActive() as the getter. Breaking the convention silently breaks serialisation and ORM mapping.
๐Ÿ’ก IDE auto-generates correct names
In IntelliJ IDEA: right-click inside a class โ†’ Generate โ†’ Getter/Setter. The IDE reads your field names and auto-generates correctly named get/set/is methods following the JavaBean convention. Always use this โ€” never hand-write getters/setters, and definitely do not deviate from the naming pattern they produce.
โญ Must-Know Interview Topics
PascalCase vs camelCase vs ALL_CAPS โ€” which for what ยท boolean getter naming ยท package naming rules ยท why conventions matter for frameworks ยท interface naming (no I prefix) ยท constant naming
Q1. What naming convention does Java use for classes, methods, and constants?
Classes & Interfaces: PascalCase (every word capitalised) โ€” StudentRecord, OrderService
Methods & Variables: camelCase (first word lowercase) โ€” calculateTotal(), firstName
Constants (static final): ALL_CAPS with underscores โ€” MAX_SIZE, PI
Packages: all lowercase, dot-separated โ€” com.example.service
Q2. How should boolean getters be named? Why does it matter?
Boolean getters should use is as the prefix instead of get: isActive(), isValid(), hasPermission().

It matters because frameworks like Jackson (JSON serialiser), Hibernate, and Spring use reflection to find getters. Jackson specifically looks for isXxx() for boolean fields. If you name it getActive(), Jackson may serialise the field as active via the getter name, causing a mismatch. Also, isActive() reads naturally as a question โ€” "is this account active?" โ€” which is better English for a boolean.
Q3. Should Java interfaces be prefixed with I (like IRunnable)?
No โ€” Java does NOT use the I prefix for interfaces. This convention comes from C# and COM programming. Java's convention is simply PascalCase for interfaces, same as classes โ€” Runnable, Comparable, Serializable, UserService.

The distinction between class and interface is visible from the implements keyword and from IDE tooling. The I prefix is considered a code smell in Java โ€” it pollutes the type name and provides no useful information that the language doesn't already express.
Q4. What is the JavaBean naming convention? Why is it important?
The JavaBean convention is a standard for writing reusable Java components. It requires:
1. A public no-arg constructor
2. Private fields
3. Public getters/setters named getFieldName() / setFieldName() (or isFieldName() for boolean)

It matters because virtually every Java framework โ€” Spring MVC, Jackson, Hibernate, JSP EL โ€” uses reflection to discover properties by finding get/set/is methods with this naming pattern. Deviating from it silently breaks property binding, JSON serialisation, and ORM mapping.
Q5. Are naming conventions enforced by the Java compiler?
No โ€” conventions are not enforced by the compiler. Your code compiles fine if you name a class studentRecord or a constant maxSize. However:
โ€” Tools enforce them: Checkstyle, PMD, SonarQube, and most IDE inspections flag convention violations
โ€” Frameworks rely on them: Spring, Hibernate, and Jackson use naming patterns to auto-wire and map fields
โ€” Team readability: any Java developer reads isActive() as a boolean getter instantly โ€” getIsActive() or checkActive() creates confusion
Breaking conventions is not a compiler error, but it is a professional error.
Q6. What is the naming convention for packages? Why lowercase?
Package names must be all lowercase, dot-separated, using reverse domain name as the root: com.companyname.projectname.module.

Reasons for lowercase:
โ€” File system compatibility: on case-sensitive file systems (Linux), com.Example and com.example are different packages. Lowercase avoids ambiguity.
โ€” Conflict avoidance: using your domain name reversed (com.google, org.apache) guarantees uniqueness globally โ€” no two organisations share the same domain.
Underscores are allowed but discouraged; hyphens are not allowed (not valid Java identifiers).

๐ŸŒฑ Naming Conventions in Spring Boot

Spring Boot has strong opinions on naming โ€” both at the Java code level and at the configuration level. Following them enables auto-configuration, reduces boilerplate, and makes the project structure immediately recognisable to any Spring developer.

โœ… Spring Boot standard naming patterns
@Controller โ†’ UserController, OrderController
@Service โ†’ UserService, OrderService (interface) + UserServiceImpl (impl)
@Repository โ†’ UserRepository, OrderRepository
@Entity โ†’ User, Order, Product (singular nouns)
DTO classes โ†’ UserDTO, CreateOrderRequest, OrderResponse
Exception classes โ†’ UserNotFoundException, InvalidOrderException
application.properties keys โ†’ kebab-case: spring.datasource.url, app.jwt-secret
Spring Boot project structure โ€” naming
// Package structure โ€” all lowercase
// com.example.ecommerce.controller
// com.example.ecommerce.service
// com.example.ecommerce.repository
// com.example.ecommerce.model
// com.example.ecommerce.dto
// com.example.ecommerce.exception

@RestController
@RequestMapping("/api/orders")
public class OrderController {         // PascalCase + "Controller" suffix

    private final OrderService orderService;  // camelCase field

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @GetMapping("/{orderId}")
    public OrderResponse getOrderById(       // camelCase method
            @PathVariable Long orderId) {       // camelCase param
        return orderService.findById(orderId);
    }
}

// application.properties โ€” kebab-case for config keys
// spring.datasource.url=jdbc:mysql://localhost/mydb
// app.jwt-secret=mySecretKey
// app.max-retry-count=3
๐Ÿ“Œ Jackson field naming โ€” camelCase โ†” snake_case
By default, Jackson serialises Java camelCase field names directly to JSON camelCase: firstName โ†’ "firstName". If your API needs snake_case JSON ("first_name"), add this to application.properties:
spring.jackson.property-naming-strategy=SNAKE_CASE
Or annotate per-class: @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
โญ Must-Know Exam Points
  • Class / Interface / Enum type / Annotation โ†’ PascalCase
  • Method / Variable / Parameter โ†’ camelCase
  • Constant (static final) โ†’ ALL_CAPS_WITH_UNDERSCORES
  • Package โ†’ all lowercase, dot-separated
  • Enum constant โ†’ ALL_CAPS (same as constants)
  • Boolean getter โ†’ is prefix, not get
  • Generic type parameter โ†’ single uppercase letter: T, E, K, V
  • Java does NOT use I prefix for interfaces (unlike C#)
  • Conventions are not compiler-enforced โ€” but framework-critical

๐Ÿ“ MCQ Traps

IdentifierCorrect ConventionCommon Wrong Answer
class nameStudentRecord (PascalCase)studentRecord or STUDENTRECORD
Method namecalculateTotal() (camelCase)CalculateTotal() or calculate_total()
static final variableMAX_SIZE (ALL_CAPS)maxSize or MaxSize
Boolean getter for activeisActive()getActive() (wrong prefix)
Package namecom.example.util (lowercase)com.Example.Util
Interface conventionRunnable (no prefix)IRunnable (C# style โ€” wrong in Java)
Enum constantPENDING (ALL_CAPS)Pending or pending

๐Ÿ“– One-Liner Definitions

  • PascalCase โ€” Every word starts with uppercase; used for classes, interfaces, enums, and annotations.
  • camelCase โ€” First word lowercase, subsequent words capitalised; used for methods, variables, and parameters.
  • ALL_CAPS โ€” All uppercase with underscores; used for static final constants and enum constants.
  • JavaBean convention โ€” Standard of public no-arg constructor, private fields, and get/set/is accessors relied on by Spring, Jackson, and Hibernate.
  • Reverse domain package naming โ€” Using reverse domain as package root (com.company.app) to guarantee global uniqueness.
TOPIC 13

Anonymous Objects

๐Ÿ‘ป What is an Anonymous Object?

An anonymous object is an object that is created without assigning it to a reference variable. You call new ClassName() and immediately use the object โ€” usually by calling a method on it โ€” without storing any reference to it.

NAMED vs ANONYMOUS OBJECT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Named object (reference stored): Student s = new Student("Rahul"); // s = reference on Stack s.display(); // can use s again later s.update(); // reusable Anonymous object (no reference): new Student("Rahul").display(); // created, used, gone // The object is immediately eligible for GC after this line // You CANNOT call any more methods on it after this statement โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Key rule: anonymous objects can only be used ONCE at the point of creation, then they become unreachable โ†’ GC eligible immediately

๐Ÿง  Memory Behaviour

ANONYMOUS OBJECT IN MEMORY โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ new Student("Rahul").display(); STACK HEAP โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ main() frame โ”‚ โ”‚ Student object โ”‚ โ”‚ (no reference โ”‚ โ”‚ name = "Rahul" โ”‚ โ”‚ variable!) โ”‚ โ”‚ โ† created here โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ display() executes โ”‚ Statement ends โ†’ 0 references โ”‚ GC eligible immediately โ†“ Compare to named object: Student s โ†’โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ [ Student object ] lives as long as s is in scope

โœ… When to Use Anonymous Objects

  • Single-use operations โ€” when you only need to call one method and never need the object again
  • Method arguments โ€” passing a newly created object directly into another method without naming it
  • Method chaining โ€” chaining a sequence of calls starting from creation
  • Reducing clutter โ€” avoids creating a named variable that is used exactly once

โŒ When NOT to Use Anonymous Objects

  • When you need to call multiple separate methods on the object after creation
  • When you need to store or retrieve state from the object later
  • When you need to pass the object to multiple places
  • When the object creation is expensive and you need to reuse it
๐Ÿ“Œ Anonymous Object vs Anonymous Class
These are two different things that share the word "anonymous":

Anonymous Object โ€” an instance of a named class, created without storing a reference. e.g. new Student().display()

Anonymous Class โ€” a class defined inline without a name, typically used to implement an interface or extend a class on the spot. e.g. new Runnable() { public void run() { ... } }

Topic 24 covers anonymous classes in detail. This topic covers anonymous objects of named classes.

๐Ÿ“ Common Patterns

ANONYMOUS OBJECT USAGE PATTERNS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Pattern 1 โ€” Direct method call: new Calculator().add(5, 3); Pattern 2 โ€” Pass as argument: printer.print(new Document("Report")); โ””โ”€โ”€โ”€โ”€ anonymous object passed in Pattern 3 โ€” Method chaining from creation: new StringBuilder() .append("Hello") .append(", World") .toString(); Pattern 4 โ€” In a loop (new object each iteration): for (int i = 0; i < 3; i++) new Dice().roll(); // fresh Dice object each roll โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

๐Ÿ’ป Program 1 โ€” Anonymous Object: Basic Usage

AnonymousBasic.java
class Greeter {
    String name;

    Greeter(String name) {
        this.name = name;
    }

    void greet() {
        System.out.println("Hello, " + name + "!");
    }

    int nameLength() {
        return name.length();
    }
}

public class AnonymousBasic {
    public static void main(String[] args) {

        // Named object โ€” reference stored, reusable
        Greeter g = new Greeter("Rahul");
        g.greet();
        System.out.println("Length: " + g.nameLength());   // can call again

        // Anonymous object โ€” used once, then unreachable
        new Greeter("Priya").greet();    // created and used in one line
        new Greeter("Arjun").greet();    // each is a fresh separate object

        // Cannot do this with anonymous โ€” no reference to call second method:
        // new Greeter("Test").greet();   โ† greet() done
        // .nameLength();                 โ† compile error โ€” nothing to chain
    }
}
โ–ถ OUTPUT
Hello, Rahul! Length: 5 Hello, Priya! Hello, Arjun!

๐Ÿ’ป Program 2 โ€” Anonymous Object as Method Argument

AnonymousAsArgument.java
class Student {
    String name;
    int    marks;
    Student(String name, int marks) {
        this.name  = name;
        this.marks = marks;
    }
}

class ReportPrinter {
    void printResult(Student s) {
        String grade = s.marks >= 90 ? "A"
                      : s.marks >= 75 ? "B"
                      : s.marks >= 60 ? "C" : "F";
        System.out.printf("%-10s | %3d | Grade: %s%n",
            s.name, s.marks, grade);
    }
}

public class AnonymousAsArgument {
    public static void main(String[] args) {

        ReportPrinter printer = new ReportPrinter();

        // Anonymous Student objects passed directly as arguments
        printer.printResult(new Student("Rahul",  92));
        printer.printResult(new Student("Priya",  78));
        printer.printResult(new Student("Arjun",  55));
        printer.printResult(new Student("Sneha",  88));
        // Each Student is created anonymously โ€” no variable name needed
        // All are GC-eligible after printResult() returns
    }
}
โ–ถ OUTPUT
Rahul | 92 | Grade: A Priya | 78 | Grade: B Arjun | 55 | Grade: F Sneha | 88 | Grade: B

๐Ÿ’ป Program 3 โ€” GC Eligibility: Named vs Anonymous

GCEligibility.java
class HeavyObject {
    static int liveCount = 0;

    HeavyObject() {
        liveCount++;
        System.out.println("Created  | live: " + liveCount);
    }

    void process() {
        System.out.println("Processing...");
    }

    protected void finalize() {   // called by GC (not guaranteed, for demo only)
        liveCount--;
        System.out.println("Finalized| live: " + liveCount);
    }
}

public class GCEligibility {
    public static void main(String[] args) {
        System.out.println("--- Anonymous object ---");
        new HeavyObject().process();   // GC eligible after this line

        System.out.println("--- Named object ---");
        HeavyObject h = new HeavyObject();
        h.process();                     // still alive โ€” h holds reference
        // h goes out of scope at end of main() โ†’ then GC eligible
    }
}
โ–ถ OUTPUT
--- Anonymous object --- Created | live: 1 Processing... --- Named object --- Created | live: 2 Processing...
โš ๏ธ Don't chain two calls on an anonymous object expecting state between them
new Student("Rahul").setAge(20).display() works only if setAge() returns this (Builder pattern). If setAge() returns void, the chain breaks โ€” you cannot call .display() after a void method. Anonymous objects only support chaining when each method returns this or another object.
๐Ÿ’ก Tip โ€” Common use in test code and one-liners
Anonymous objects shine in test assertions and one-shot prints where creating a named variable adds noise. e.g. System.out.println(new Calculator().add(5, 3)); is more readable than:
Calculator c = new Calculator();
System.out.println(c.add(5, 3));
The named version is only worthwhile if c is used more than once.
โญ Must-Know Interview Topics
Anonymous object definition ยท when GC eligible ยท use cases ยท anonymous object vs anonymous class distinction ยท limitation (single use) ยท method chaining requirement
Q1. What is an anonymous object? How is it different from a named object?
An anonymous object is an object created with new ClassName() without assigning it to a reference variable. It can be used only at the point of creation โ€” typically to call one method or pass it as an argument.

A named object has a reference variable stored on the Stack that points to it. It can be accessed multiple times as long as the reference is in scope.

Key difference: an anonymous object has zero references from the moment its single expression finishes โ€” making it immediately GC-eligible. A named object lives until its reference variable goes out of scope.
Q2. When does an anonymous object become eligible for garbage collection?
An anonymous object becomes GC-eligible immediately after the statement that created it completes. Since no reference variable holds its address, there are zero references pointing to it the moment the expression finishes evaluating.

For example, in new Student("Rahul").display(); โ€” after display() returns and the semicolon is reached, the Student object has no references and is eligible for GC. This is earlier than a named object, which lives until its reference variable goes out of scope.
Q3. What are the use cases for anonymous objects?
1. Single method call โ€” when you only need to call one method and discard the object: new Logger().log("msg")
2. Method argument โ€” pass a freshly created object into another method without naming it: processor.handle(new Request("GET", "/api"))
3. Method chaining โ€” start a fluent chain from creation: new StringBuilder().append("a").append("b").toString()
4. One-shot computation โ€” compute and immediately use a result: System.out.println(new Calculator().factorial(5))
Q4. What is the difference between an anonymous object and an anonymous class?
Anonymous object: an instance of a named class, created without a reference variable. The class already exists; you just don't store the object. e.g. new Student().display()

Anonymous class: a class defined inline with no name, typically to implement an interface or override methods of a class on the spot. It both defines the class and creates one instance simultaneously. e.g.:
Runnable r = new Runnable() { public void run() { ... } };

These are fundamentally different concepts. Anonymous objects are about object lifetime. Anonymous classes are about class definition โ€” covered in Topic 24.
Q5. Can you call multiple methods on an anonymous object?
Only through method chaining โ€” not separately.

You cannot do:
new Student("Rahul").greet();
// and then separately: .display(); โ€” there is no reference to chain off.

You can do chaining if each method returns this:
new Builder().setName("Rahul").setAge(20).build();

But if any method returns void, the chain ends there and no further calls are possible on that object.
Q6. Are anonymous objects stored on Stack or Heap?
The object itself is on the Heap โ€” all Java objects live in the Heap regardless of whether they are named or anonymous. The difference is that a named object has a reference variable on the Stack pointing to it, while an anonymous object has no Stack reference.

Internally, the JVM may use a temporary implicit reference during expression evaluation, but that reference is never stored in any named variable and is released as soon as the expression completes.

๐ŸŒฑ Anonymous Objects in Spring Boot

Anonymous objects appear frequently in Spring Boot in three contexts: building request/response objects inline, writing test assertions, and constructing entities/DTOs in service layer code.

โœ… Common anonymous object patterns in Spring Boot
Building ResponseEntity inline โ€” create and return in one expression
Test assertions โ€” create expected object anonymously for comparison
Exception throwing โ€” throw new ResourceNotFoundException("User not found")
DTO construction โ€” build and immediately pass a DTO to a service method
UserController.java โ€” Spring Boot anonymous object patterns
@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public ResponseEntity<UserDTO> createUser(
            @RequestBody CreateUserRequest request) {

        // Anonymous UserDTO created inline and returned immediately
        return ResponseEntity.ok(
            new UserDTO(userService.createUser(request)));
    }

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id)
            // Anonymous exception โ€” most common anonymous object in Spring!
            .orElseThrow(() -> new RuntimeException("User not found: " + id));
    }
}
๐Ÿ“Œ throw new Exception(...) is always an anonymous object
Every time you write throw new ResourceNotFoundException("msg"), you are creating an anonymous object โ€” a new exception instance with no reference variable. It is passed directly to the JVM's exception handling machinery. This is the single most common anonymous object pattern in all Java/Spring Boot code.
โญ Must-Know Exam Points
  • Anonymous object = object created without a reference variable
  • Anonymous object is on the Heap โ€” all objects are; it just has no Stack reference
  • GC eligible immediately after the statement finishes โ€” zero references
  • Can only be used once โ€” at the point of creation
  • Can chain methods only if each returns this or another object โ€” not after void
  • Best for: single method calls, method arguments, one-shot computations
  • Anonymous object โ‰  Anonymous class โ€” completely different concepts
  • throw new Exception("msg") is the most common anonymous object in Java

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Where does an anonymous object live?HeapAll objects live in Heap โ€” anonymous or not; only the Stack reference is missing
When is anonymous object GC eligible?After the statement endsNot "immediately" in a vague sense โ€” specifically after the expression/statement that created it completes
Can you call two separate methods on an anonymous object?No (unless chained via return this)No reference = no second access point
Is new X().method() valid Java?YesPerfectly legal โ€” creates object, calls method, object becomes unreachable
Anonymous object vs anonymous class?Different thingsAnonymous object = instance of named class, no reference; Anonymous class = inline class definition

๐Ÿ“– One-Liner Definitions

  • Anonymous object โ€” An object created with new but not assigned to any reference variable; usable only once at the point of creation.
  • GC eligibility โ€” An object becomes eligible for garbage collection when no live reference points to it; anonymous objects reach this state immediately after their creating expression completes.
  • Method chaining on anonymous object โ€” Possible only when each method in the chain returns this or another object, enabling the next call to have a receiver.
TOPIC 14

Inheritance โ€” Introduction & All Types

๐Ÿงฌ What is Inheritance?

Inheritance is an OOP mechanism where a child class (subclass) acquires the properties and behaviours of a parent class (superclass). It models the IS-A relationship โ€” a Dog IS-A Animal, a Car IS-A Vehicle, a SavingsAccount IS-A BankAccount.

Inheritance promotes code reuse โ€” common fields and methods are written once in the parent and inherited by all children, avoiding duplication. It also enables polymorphism (Topic 19).

๐Ÿ“Œ IS-A vs HAS-A
IS-A (inheritance): Dog is a Animal โ†’ use extends
HAS-A (composition): Car has a Engine โ†’ use a field of type Engine

A common design mistake is using inheritance when composition is more appropriate. Rule of thumb: if you cannot truthfully say "X IS-A Y", don't use inheritance โ€” use composition instead.

๐Ÿ”‘ The extends Keyword

BASIC INHERITANCE SYNTAX โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Animal { // Parent / Superclass / Base class String name; void eat() { ... } void sleep(){ ... } } class Dog extends Animal { // Child / Subclass / Derived class String breed; // Dog's own field void bark() { ... } // Dog's own method // Inherited from Animal: // name, eat(), sleep() โ† available without redeclaring } Dog d = new Dog(); d.name = "Bruno"; // inherited field d.eat(); // inherited method d.bark(); // own method โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

๐Ÿ“ What IS and IS NOT Inherited

Inherited โœ…NOT Inherited โŒ
Public & protected fieldsPrivate fields (hidden but accessible via public getters)
Public & protected methodsPrivate methods
Default (package) members โ€” same package onlyConstructors (called via super(), not inherited)
Static members (accessible, not truly "inherited")Static members cannot be overridden (only hidden)

๐ŸŒณ Types of Inheritance in Java

1. Single-Level Inheritance

SINGLE-LEVEL โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Animal โ””โ”€โ”€ Dog class Animal { void eat() {} } class Dog extends Animal { void bark() {} } One parent โ†’ one child. Simplest form.

2. Multi-Level Inheritance

MULTI-LEVEL โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Animal โ””โ”€โ”€ Dog โ””โ”€โ”€ GoldenRetriever class Animal { void eat() {} } class Dog extends Animal { void bark() {} } class GoldenRetriever extends Dog { void fetch() {} } GoldenRetriever gr = new GoldenRetriever(); gr.eat(); // from Animal (grandparent) gr.bark(); // from Dog (parent) gr.fetch(); // own method Chain can be any length โ€” each level inherits ALL above levels.

3. Hierarchical Inheritance

HIERARCHICAL โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Animal โ”œโ”€โ”€ Dog โ”œโ”€โ”€ Cat โ””โ”€โ”€ Bird class Dog extends Animal { void bark() {} } class Cat extends Animal { void meow() {} } class Bird extends Animal { void chirp() {} } Multiple children share the same parent. All three inherit eat() and sleep() from Animal.

4. Multiple Inheritance โ€” NOT Supported for Classes

MULTIPLE INHERITANCE โ€” DIAMOND PROBLEM โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โŒ Java does NOT allow a class to extend two classes: class C extends A, B { } โ† COMPILE ERROR Why? The Diamond Problem: A / B C Both B and C override A's method show() \ / D D extends B and C โ†’ which show() does D use? B's or C's? Ambiguous! โ†’ Compile error. โœ… Java's solution: interfaces support multiple "inheritance" (via default methods) โ€” covered in Topic 25. A class CAN implement multiple interfaces.

5. Hybrid Inheritance

A combination of two or more inheritance types. Achievable in Java through a mix of class inheritance and interface implementation. Since classes can only extend one class but implement multiple interfaces, true hybrid inheritance uses interfaces for the "multiple" part.

โš ๏ธ Java supports only single class inheritance
A Java class can extend exactly one class (or implicitly extends Object if none specified). Attempting to extend two classes is a compile error. For multiple inheritance of behaviour, Java uses interfaces โ€” a class can implement any number of interfaces.

๐Ÿ›๏ธ The Object Class โ€” Root of All

Every class in Java implicitly extends java.lang.Object if it doesn't explicitly extend another class. This means every object has methods like toString(), equals(), hashCode(), getClass(), clone(), and finalize() available โ€” inherited from Object.

OBJECT AS ROOT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Object โ† root of all Java classes โ””โ”€โ”€ Animal โ””โ”€โ”€ Dog โ””โ”€โ”€ GoldenRetriever class Animal { } // compiler inserts: class Animal extends Object { } GoldenRetriever.toString() // inherited from Object GoldenRetriever.equals(obj) // inherited from Object GoldenRetriever.hashCode() // inherited from Object

๐Ÿ’ป Program 1 โ€” Single & Hierarchical Inheritance

HierarchicalInheritance.java
class Animal {
    String name;

    Animal(String name) { this.name = name; }

    void eat() {
        System.out.println(name + " is eating");
    }
    void sleep() {
        System.out.println(name + " is sleeping");
    }
}

// Hierarchical: Dog and Cat both extend Animal
class Dog extends Animal {
    Dog(String name) { super(name); }  // call parent constructor
    void bark() { System.out.println(name + " says: Woof!"); }
}

class Cat extends Animal {
    Cat(String name) { super(name); }
    void meow() { System.out.println(name + " says: Meow!"); }
}

public class HierarchicalInheritance {
    public static void main(String[] args) {
        Dog dog = new Dog("Bruno");
        dog.eat();    // inherited from Animal
        dog.sleep();  // inherited from Animal
        dog.bark();   // own method

        Cat cat = new Cat("Whiskers");
        cat.eat();    // inherited from Animal
        cat.meow();   // own method
    }
}
โ–ถ OUTPUT
Bruno is eating Bruno is sleeping Bruno says: Woof! Whiskers is eating Whiskers says: Meow!

๐Ÿ’ป Program 2 โ€” Multi-Level Inheritance

MultiLevelInheritance.java
class Vehicle {
    String brand;
    int    speed;

    Vehicle(String brand, int speed) {
        this.brand = brand;
        this.speed = speed;
    }
    void move() {
        System.out.println(brand + " moving at " + speed + " km/h");
    }
}

class Car extends Vehicle {
    int doors;

    Car(String brand, int speed, int doors) {
        super(brand, speed);   // call Vehicle constructor
        this.doors = doors;
    }
    void honk() {
        System.out.println(brand + " honks! Doors: " + doors);
    }
}

class ElectricCar extends Car {
    int batteryRange;

    ElectricCar(String brand, int speed, int doors, int batteryRange) {
        super(brand, speed, doors);   // call Car constructor
        this.batteryRange = batteryRange;
    }
    void charge() {
        System.out.println(brand + " charging | range: " + batteryRange + " km");
    }
}

public class MultiLevelInheritance {
    public static void main(String[] args) {
        ElectricCar tesla = new ElectricCar("Tesla", 250, 4, 600);
        tesla.move();    // from Vehicle (grandparent)
        tesla.honk();    // from Car (parent)
        tesla.charge();  // own method

        // instanceof checks the entire chain
        System.out.println(tesla instanceof ElectricCar);  // true
        System.out.println(tesla instanceof Car);          // true
        System.out.println(tesla instanceof Vehicle);      // true
    }
}
โ–ถ OUTPUT
Tesla moving at 250 km/h Tesla honks! Doors: 4 Tesla charging | range: 600 km true true true

๐Ÿ’ป Program 3 โ€” Diamond Problem Demo (Why Java Blocks Multiple Class Inheritance)

DiamondProblem.java โ€” interfaces solve what classes cannot
// Classes: single inheritance only
class A { void show() { System.out.println("A"); } }
class B extends A { void show() { System.out.println("B"); } }
class C extends A { void show() { System.out.println("C"); } }
// class D extends B, C { }  โ† COMPILE ERROR โ€” multiple inheritance

// โœ… Solution: interfaces CAN have multiple parents
interface Flyable    { default void fly()   { System.out.println("Flying");  } }
interface Swimmable  { default void swim()  { System.out.println("Swimming"); } }

// A class can implement MULTIPLE interfaces
class Duck extends A implements Flyable, Swimmable {
    void quack() { System.out.println("Quack!"); }
}

public class DiamondProblem {
    public static void main(String[] args) {
        Duck duck = new Duck();
        duck.show();   // from A (class inheritance)
        duck.fly();    // from Flyable (interface)
        duck.swim();   // from Swimmable (interface)
        duck.quack();  // own method
    }
}
โ–ถ OUTPUT
A Flying Swimming Quack!
โš ๏ธ Avoid deep inheritance chains
Multi-level inheritance is powerful but chains longer than 3โ€“4 levels become hard to follow. Changes to the top of the chain ripple all the way down. The real-world advice: prefer composition over deep inheritance. If you find yourself building a chain 5+ levels deep, consider extracting shared behaviour into interfaces or helper classes instead.
๐Ÿ’ก instanceof checks the full chain
instanceof returns true for the actual type and every type in its inheritance chain. An ElectricCar is an instance of ElectricCar, Car, Vehicle, and Object. This is the foundation of polymorphism โ€” you can treat a child object as its parent type.
โญ Must-Know Interview Topics
IS-A vs HAS-A ยท extends keyword ยท what is/isn't inherited ยท 5 types of inheritance ยท why Java blocks multiple class inheritance ยท Diamond Problem ยท Object as root ยท instanceof behaviour
Q1. What is inheritance? What is the IS-A relationship?
Inheritance is an OOP mechanism where a subclass acquires fields and methods of its superclass using the extends keyword. It models the IS-A relationship: a Dog IS-A Animal, so Dog inherits Animal's behaviour.

Purpose: code reuse (define common behaviour once in parent), and the foundation for polymorphism (a parent reference can hold a child object).
Q2. What is and is NOT inherited from the parent class?
Inherited: public and protected fields, public and protected methods, default (package-access) members if in the same package, static members (accessible but not truly overridable).

NOT inherited: private fields and methods (they exist in the object but are not accessible), constructors (they can be called via super() but are never inherited and cannot be overridden).
Q3. Why does Java not support multiple class inheritance?
Java avoids the Diamond Problem. If class D extends both B and C, and both B and C override the same method from A, the compiler cannot determine which version D should inherit โ€” the ambiguity makes the code unreliable.

Java's solution: a class can extend only ONE class, but can implement multiple interfaces. Interfaces handle the multiple-inheritance-of-behaviour scenario with clearer rules (the implementing class must provide its own override if there is ambiguity between default methods).
Q4. What are the types of inheritance in Java?
Single-level: one parent โ†’ one child
Multi-level: A โ†’ B โ†’ C (chain of inheritance)
Hierarchical: one parent โ†’ multiple children
Multiple: not supported for classes (Diamond Problem); supported via interfaces
Hybrid: combination โ€” achieved in Java via class inheritance + multiple interface implementation
Q5. What is the role of the Object class in Java's inheritance?
java.lang.Object is the root of every class hierarchy in Java. Every class implicitly extends Object if it doesn't explicitly extend another class. This guarantees that every Java object has a common set of methods: toString(), equals(), hashCode(), getClass(), clone(), finalize(), wait(), notify(), notifyAll().

This is why Object obj = new Student() is always valid โ€” every class IS-A Object.
Q6. What is the difference between IS-A and HAS-A relationships?
IS-A (inheritance): the child class is a specialisation of the parent. Implemented with extends. Example: Dog extends Animal โ€” a Dog is an Animal.

HAS-A (composition): one class contains another as a field. Example: Car has an Engine field โ€” a Car has an Engine.

Design rule: use inheritance only when IS-A is genuinely true. If you're tempted to inherit just to reuse code but the IS-A relationship is weak, use composition instead โ€” it's more flexible and avoids tight coupling.
Q7. What does instanceof return for objects in an inheritance chain?
instanceof returns true if the object is an instance of the specified type or any of its supertypes.

For ElectricCar ec = new ElectricCar():
ec instanceof ElectricCar โ†’ true
ec instanceof Car โ†’ true
ec instanceof Vehicle โ†’ true
ec instanceof Object โ†’ true

This is the IS-A relationship made explicit at runtime. It is used before downcasting to prevent ClassCastException.

๐ŸŒฑ Inheritance in Spring Boot

Inheritance is used throughout Spring Boot โ€” in entity hierarchies, exception hierarchies, base entity classes, and service layering. Understanding it helps you write DRY, maintainable Spring code.

โœ… Common inheritance patterns in Spring Boot
BaseEntity โ€” common audit fields (id, createdAt, updatedAt) extracted to a parent entity
Exception hierarchy โ€” custom exceptions extending RuntimeException
@MappedSuperclass โ€” JPA annotation for a parent entity whose fields are mapped to child tables
Abstract service โ€” shared service logic in a base class, specialised by concrete services
BaseEntity.java + User.java + AppException.java โ€” Spring Boot
// โ”€โ”€ @MappedSuperclass โ€” fields inherited by all entity tables โ”€โ”€
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
    @Id @GeneratedValue
    private Long id;

    @CreatedDate
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;
}

// User inherits id, createdAt, updatedAt from BaseEntity
@Entity
public class User extends BaseEntity {  // IS-A BaseEntity
    private String name;
    private String email;
}

// โ”€โ”€ Exception hierarchy โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
public class AppException extends RuntimeException {
    private final int statusCode;
    public AppException(String msg, int statusCode) {
        super(msg);
        this.statusCode = statusCode;
    }
    public int getStatusCode() { return statusCode; }
}

public class UserNotFoundException extends AppException {
    public UserNotFoundException(Long id) {
        super("User not found: " + id, 404);
    }
}
๐Ÿ“Œ @MappedSuperclass vs @Entity for parent
@MappedSuperclass: parent class fields are mapped to each child's own table โ€” no separate table for the parent. Used for shared audit fields.
@Entity + @Inheritance: parent gets its own table. Complex JPA inheritance mapping (SINGLE_TABLE, JOINED, TABLE_PER_CLASS strategies). Most Spring Boot projects use @MappedSuperclass for the base audit entity pattern.
โญ Must-Know Exam Points
  • Inheritance keyword: extends (for classes), implements (for interfaces)
  • Java supports single class inheritance only โ€” one extends per class
  • Private members are NOT inherited (exist in object but not accessible)
  • Constructors are NOT inherited โ€” called via super()
  • Every class implicitly extends Object if no explicit parent
  • Diamond Problem โ†’ reason Java blocks multiple class inheritance
  • instanceof returns true for the object's type AND all supertypes
  • IS-A = inheritance; HAS-A = composition (field)
  • Multi-level: A โ†’ B โ†’ C; Hierarchical: A โ†’ B, A โ†’ C

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Can a class extend two classes?NoDiamond Problem โ€” compile error
Are constructors inherited?NoCalled via super() but not inherited or overridden
Are private fields inherited?No (not accessible)They exist in the object memory but subclass cannot access them directly
What does every class implicitly extend?Objectjava.lang.Object is the universal root
Dog d = new Dog(); d instanceof Animal?trueinstanceof checks the full chain, not just direct type
Can a class implement multiple interfaces?YesOnly extends is limited to one; implements has no limit
What type of inheritance does Aโ†’Bโ†’C represent?Multi-levelNot hierarchical (that is one parent, many children)

๐Ÿ“– One-Liner Definitions

  • Inheritance โ€” Mechanism where a subclass acquires fields and methods of its superclass via extends.
  • IS-A relationship โ€” The semantic contract of inheritance: a Dog IS-A Animal; used to validate whether inheritance is appropriate.
  • extends โ€” Keyword used by a class to inherit from exactly one parent class.
  • Multi-level inheritance โ€” A chain: A โ†’ B โ†’ C, where C inherits from B which inherits from A.
  • Hierarchical inheritance โ€” One parent, multiple children: A โ†’ B, A โ†’ C, A โ†’ D.
  • Diamond Problem โ€” Ambiguity arising when a class tries to inherit the same method from two parents; the reason Java blocks multiple class inheritance.
  • @MappedSuperclass โ€” JPA annotation for a non-entity parent whose fields are mapped into each child entity's table.
TOPIC 15

super Keyword & Constructor Chaining

๐Ÿ”‘ The super Keyword โ€” Three Uses

The super keyword is a reference to the immediate parent class. It has three distinct uses: accessing a parent field, calling a parent method, and calling a parent constructor.

THREE USES OF super โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. super.field โ†’ access parent's field (when hidden by child) 2. super.method() โ†’ call parent's method (when overridden by child) 3. super() โ†’ call parent's constructor (MUST be first line) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

Use 1 โ€” Accessing Parent Field

FIELD HIDING (variable shadowing) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Animal { String type = "Animal"; } class Dog extends Animal { String type = "Dog"; // hides Animal's type void showTypes() { System.out.println(type); // "Dog" (own field) System.out.println(super.type); // "Animal" (parent field) } } โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Note: field hiding is rare and discouraged in practice. Use super.field only when you intentionally need the parent's version of a hidden field.

Use 2 โ€” Calling Parent Method

CALLING OVERRIDDEN PARENT METHOD โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Animal { void sound() { System.out.println("Some sound"); } } class Dog extends Animal { void sound() { // overrides Animal.sound() super.sound(); // calls Animal's version first System.out.println("Woof!"); // then adds Dog behaviour } } new Dog().sound(); Output: Some sound โ† from super.sound() Woof! โ† from Dog's own code โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Use case: extending parent behaviour rather than replacing it.

Use 3 โ€” Calling Parent Constructor (super())

The most important use. When a child class constructor must initialise parent fields, it calls the parent constructor via super(args). This must be the very first statement in the child constructor.

super() CONSTRUCTOR CALL RULES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. super() must be the FIRST statement in a constructor 2. You can call super() OR this() as first statement โ€” not both 3. If you write no super() or this(), compiler inserts super() automatically (calls parent's no-arg constructor) 4. If parent has no no-arg constructor, compiler's implicit super() fails โ†’ compile error in child class class Animal { String name; Animal(String name) { this.name = name; } // NO no-arg constructor! } class Dog extends Animal { Dog(String name) { super(name); // MUST call Animal(String) explicitly // without this line โ†’ compile error } } โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

๐Ÿ”— Constructor Chaining

Constructor chaining is when one constructor calls another. There are two kinds:

  • this() chaining โ€” calling another constructor in the same class (covered in Topic 9)
  • super() chaining โ€” calling a constructor in the parent class
CONSTRUCTOR CHAINING ACROSS HIERARCHY โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ new ElectricCar("Tesla", 250, 4, 600) triggers: ElectricCar(String, int, int, int) calls super(brand, speed, doors) | v Car(String, int, int) calls super(brand, speed) | v Vehicle(String, int) calls super() โ† implicit | v Object() โ† root constructor Each constructor completes from the BOTTOM UP: Object โ†’ Vehicle โ†’ Car โ†’ ElectricCar (initialisation flows up first, then returns down) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

๐Ÿ›๏ธ Object Class โ€” Implicit Root Constructor

Every constructor chain eventually reaches Object(). Even if you don't write super() in your constructor, the compiler inserts it automatically, and this propagates all the way to Object. This guarantees that the Object part of every object is always properly initialised.

๐Ÿ“Œ this() vs super() โ€” mutual exclusion
Both this() and super() must be the first statement in a constructor. Since a constructor can only have one first statement, you cannot use both in the same constructor. However, a chain is fine:
Constructor A calls this() โ†’ Constructor B calls super()
The rule is: every chain must eventually reach a super() call (explicit or implicit) so Object is initialised.

๐Ÿ’ป Program 1 โ€” All Three Uses of super

SuperKeyword.java
class Animal {
    String type = "Animal";           // Use 1: field that child will hide
    String name;

    Animal(String name) {              // Use 3: parent constructor
        this.name = name;
        System.out.println("Animal constructor: " + name);
    }

    void sound() {                       // Use 2: method child will override
        System.out.println(name + " makes a sound");
    }
}

class Dog extends Animal {
    String type = "Dog";              // hides Animal.type
    String breed;

    // Use 3: super() โ€” calls Animal constructor
    Dog(String name, String breed) {
        super(name);                    // MUST be first line
        this.breed = breed;
        System.out.println("Dog constructor: " + name + " / " + breed);
    }

    // Use 2: super.method() โ€” calls Animal's sound(), then adds own
    void sound() {
        super.sound();                  // "Bruno makes a sound"
        System.out.println(name + " says: Woof!");
    }

    void showTypes() {
        // Use 1: super.field โ€” access hidden parent field
        System.out.println("Own type  : " + type);         // "Dog"
        System.out.println("super type: " + super.type);   // "Animal"
    }
}

public class SuperKeyword {
    public static void main(String[] args) {
        Dog dog = new Dog("Bruno", "Labrador");
        dog.sound();
        dog.showTypes();
    }
}
โ–ถ OUTPUT
Animal constructor: Bruno Dog constructor: Bruno / Labrador Bruno makes a sound Bruno says: Woof! Own type : Dog super type: Animal

๐Ÿ’ป Program 2 โ€” Constructor Chaining: Object โ†’ Vehicle โ†’ Car โ†’ ElectricCar

ConstructorChain.java
class Vehicle {
    String brand;
    Vehicle(String brand) {
        this.brand = brand;
        System.out.println("1. Vehicle()");
    }
}

class Car extends Vehicle {
    int doors;
    Car(String brand, int doors) {
        super(brand);            // calls Vehicle()
        this.doors = doors;
        System.out.println("2. Car()");
    }
}

class ElectricCar extends Car {
    int range;
    ElectricCar(String brand, int doors, int range) {
        super(brand, doors);     // calls Car()
        this.range = range;
        System.out.println("3. ElectricCar()");
    }
}

public class ConstructorChain {
    public static void main(String[] args) {
        System.out.println("Creating ElectricCar:");
        ElectricCar ec = new ElectricCar("Tesla", 4, 600);
        System.out.printf("%s | %d doors | %dkm range%n",
            ec.brand, ec.doors, ec.range);
    }
}
โ–ถ OUTPUT
Creating ElectricCar: 1. Vehicle() 2. Car() 3. ElectricCar() Tesla | 4 doors | 600km range

๐Ÿ’ป Program 3 โ€” Compile Error: Missing super() When Parent Has No No-Arg

MissingSuperError.java โ€” understanding the compile error
class Parent {
    String name;
    // Only parameterized constructor โ€” no no-arg!
    Parent(String name) { this.name = name; }
}

class ChildBroken extends Parent {
    ChildBroken() {
        // compiler tries to insert super() here
        // but Parent has no no-arg constructor!
        // COMPILE ERROR: There is no default constructor available in Parent
    }
}

class ChildFixed extends Parent {
    ChildFixed(String name) {
        super(name);             // explicitly calls Parent(String) โ€” OK
        System.out.println("ChildFixed created: " + name);
    }
}

public class MissingSuperError {
    public static void main(String[] args) {
        ChildFixed c = new ChildFixed("Rahul");
        System.out.println("name from parent: " + c.name);
    }
}
โ–ถ OUTPUT
ChildFixed created: Rahul name from parent: Rahul
โš ๏ธ super() must be the first statement โ€” no exceptions
Placing any other statement before super() or this() in a constructor is a compile error. You cannot do: System.out.println("hi"); super(name); โ€” the print must come after. This is enforced strictly by the compiler.
๐Ÿ’ก When super.method() is essential
Use super.method() when you want to extend rather than replace the parent's behaviour. Common in GUI frameworks (call super.paintComponent(g) before adding your own drawing) and in Spring (sometimes calling super.configure(http) in security config). If you override a method and never call super.method(), you are fully replacing the parent's logic โ€” make sure that is your intent.
โญ Must-Know Interview Topics
Three uses of super ยท super() must be first ยท this() vs super() mutual exclusion ยท constructor chain order (top-down init, bottom-up execution) ยท implicit super() ยท compile error when parent has no no-arg
Q1. What are the three uses of the super keyword?
1. super.field โ€” access a parent field that is hidden (shadowed) by a field of the same name in the child class.
2. super.method() โ€” call the parent's version of a method that the child has overridden. Used when you want to extend rather than fully replace the parent's behaviour.
3. super(args) โ€” call the parent class constructor. Must be the first statement in the child constructor. Used to initialise the parent part of the object.
Q2. What happens if you don't write super() in a child constructor?
The compiler automatically inserts a no-argument super() as the first statement of every constructor that doesn't already start with super() or this().

If the parent class has a no-arg constructor, this works fine silently. But if the parent has only parameterized constructors (no no-arg), the implicit super() fails to find a match โ†’ compile error: "There is no default constructor available in ParentClass". The fix: explicitly call the correct super(args).
Q3. Can you use both this() and super() in the same constructor?
No โ€” both must be the first statement, so only one can appear in any single constructor.

However, they can work together in a chain: Constructor A uses this() to delegate to Constructor B, and Constructor B then uses super() to call the parent. The rule is simply that each constructor can only have one of them as its first statement. Every chain must ultimately reach a super() (explicit or implicit) to ensure the parent is initialised.
Q4. In what order do constructors execute in a multi-level hierarchy?
Constructor bodies execute from top to bottom (parent first, child last) โ€” but the chain is triggered from the bottom up.

For new ElectricCar():
1. ElectricCar() calls super() โ†’ pauses
2. Car() calls super() โ†’ pauses
3. Vehicle() calls super() โ†’ pauses
4. Object() completes first
5. Vehicle() body completes
6. Car() body completes
7. ElectricCar() body completes last

This ensures every parent is fully initialised before a child uses it. Output: parent print messages appear before child print messages.
Q5. What is the difference between super.method() and just calling the method?
Calling a method without super inside a child class uses dynamic dispatch โ€” the JVM resolves the method based on the actual runtime type of the object. If the child overrides the method, the child's version always runs.

super.method() bypasses dynamic dispatch and directly calls the parent's version of the method, regardless of overriding. It is a static, compile-time resolution.

Practical use: in an overriding method, call super.method() to execute the parent's logic first, then add the child's extra behaviour โ€” rather than rewriting the parent's logic from scratch.
Q6. Can super() be called from a static method?
No โ€” super cannot be used in a static context. The super keyword refers to the parent part of the current object (this). Static methods have no this reference โ€” they belong to the class, not to an instance. Therefore, both super.field, super.method(), and super() are all illegal inside a static method. The compiler will give an error: "Non-static variable super cannot be referenced from a static context."

๐ŸŒฑ super in Spring Boot

The super keyword appears in Spring Boot primarily when extending framework classes, building exception hierarchies, and calling parent constructors in entity/service base classes.

โœ… Common super patterns in Spring Boot
Custom exceptions โ€” super(message) and super(message, cause) to pass details to RuntimeException
BaseEntity โ€” child entity calls parent constructor if base has one
WebSecurityConfigurerAdapter โ€” calling super.configure(http) (legacy Spring Security)
Abstract service โ€” child service calls super.validate() to reuse parent validation logic
Exception hierarchy + Abstract service โ€” Spring Boot super patterns
// โ”€โ”€ Custom exception using super() โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
public class AppException extends RuntimeException {
    private final int httpStatus;

    public AppException(String message, int httpStatus) {
        super(message);        // passes message to RuntimeException
        this.httpStatus = httpStatus;
    }
    public int getHttpStatus() { return httpStatus; }
}

public class ResourceNotFoundException extends AppException {
    public ResourceNotFoundException(String resource, Long id) {
        super(resource + " not found with id: " + id, 404);
    }
}

// โ”€โ”€ Abstract base service โ€” super.method() to reuse logic โ”€โ”€โ”€โ”€
public abstract class BaseService<T> {
    protected void validateNotNull(T entity) {
        if (entity == null)
            throw new IllegalArgumentException("Entity must not be null");
    }
}

@Service
public class UserService extends BaseService<User> {
    public void save(User user) {
        super.validateNotNull(user);  // reuse parent validation
        // ... additional UserService-specific logic
    }
}
๐Ÿ“Œ Exception chaining with super(message, cause)
When wrapping a lower-level exception in a custom one, pass the original exception as the cause:
super(message, originalException)
This preserves the full stack trace, making debugging much easier. Spring's @ControllerAdvice can then unwrap and log it properly.
โญ Must-Know Exam Points
  • super has three uses: access field, call method, call constructor
  • super() must be the first statement in a constructor
  • this() and super() cannot both appear in the same constructor
  • If no super() or this() written, compiler inserts super() automatically
  • If parent has no no-arg constructor โ†’ implicit super() fails โ†’ compile error
  • Constructor bodies execute parent first, child last (top-down)
  • super cannot be used in a static context
  • Every chain eventually reaches Object()

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Where must super() appear in a constructor?First statementAny other position is a compile error
Can you use super() and this() together?No โ€” in same constructorBoth require first-statement position; only one can be first
What does compiler insert if no super() or this()?super() โ€” no-argAlways inserted; fails if parent has no no-arg constructor
In what order do constructor bodies run?Parent first, child lastObject runs first, deepest child runs last
Can super be used in a static method?Nosuper needs this reference โ€” unavailable in static context
super.method() โ€” which method does it call?Immediate parent's versionBypasses dynamic dispatch; not necessarily grandparent

๐Ÿ“– One-Liner Definitions

  • super keyword โ€” Reference to the immediate parent class; used to access hidden fields, call overridden methods, or invoke parent constructors.
  • super() โ€” Calls the parent class constructor; must be the first statement in a child constructor; implicitly inserted by compiler if omitted.
  • Constructor chaining โ€” A sequence of constructor calls (via this() or super()) that propagates up the inheritance hierarchy to Object.
  • Implicit super() โ€” The no-arg parent constructor call inserted automatically by the compiler when no explicit super() or this() is written.
  • super.method() โ€” Directly invokes the parent class's version of an overridden method, bypassing runtime polymorphism.
TOPIC 16

Method Overriding

๐Ÿ”„ What is Method Overriding?

Method overriding occurs when a subclass provides its own implementation of a method that is already defined in its superclass, using the same name, same parameter list, and same (or covariant) return type. The subclass version replaces the parent version for objects of that subclass.

Overriding is the mechanism behind runtime polymorphism โ€” the JVM decides at runtime which version to call based on the actual type of the object, not the reference type.

METHOD OVERRIDING โ€” BASIC STRUCTURE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Animal { void sound() { System.out.println("Some sound"); } } class Dog extends Animal { @Override // annotation โ€” optional but strongly recommended void sound() { System.out.println("Woof!"); } } class Cat extends Animal { @Override void sound() { System.out.println("Meow!"); } } Animal a = new Dog(); // parent reference, child object a.sound(); // Output: Woof! โ† Dog's version called // Decided at RUNTIME โ€” not compile time

๐Ÿ“‹ Rules for Method Overriding

RuleDetail
Same method nameExact same name as in parent
Same parameter listSame number, types, and order of parameters (different = overloading, not overriding)
Same or covariant return typeReturn type must be same or a subtype of the parent's return type
Access modifierMust be same or wider โ€” can widen (protected โ†’ public) but NOT narrow (public โ†’ private)
Checked exceptionsCan throw fewer or narrower checked exceptions โ€” cannot throw new or broader ones
IS-A requiredOnly possible between parent and child class (inheritance required)

โŒ What CANNOT Be Overridden

CANNOT OVERRIDE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. static methods โ†’ they are hidden, not overridden (method hiding โ€” resolved at compile time) 2. private methods โ†’ not inherited; child can define same name but it is a new method, not an override 3. final methods โ†’ compiler enforces โ€” compile error if attempted 4. Constructors โ†’ constructors are never inherited or overridden Example of method HIDING (static): class A { static void show() { print("A"); } } class B extends A { static void show() { print("B"); } } A obj = new B(); obj.show(); // prints "A" โ† resolved at compile time via reference type // NOT "B" โ€” this is NOT polymorphism!

๐Ÿ”ฌ Overriding vs Overloading โ€” Side by Side

OverridingOverloading
WhereSubclass overrides parent methodSame class (or subclass), different signature
SignatureSame name + same paramsSame name + different params
Return typeSame or covariantCan be anything (doesn't affect resolution)
Resolved atRuntime (dynamic dispatch)Compile time (static dispatch)
PolymorphismRuntime polymorphismCompile-time polymorphism
@OverrideApplicable and recommendedNot applicable
InheritanceRequiredNot required

๐Ÿ“Œ @Override Annotation

The @Override annotation tells the compiler: "I intend this method to override a parent method." If the signature doesn't match any parent method, the compiler gives an error โ€” catching typos and signature mismatches before runtime. Always use it.

โš ๏ธ Forgetting @Override silently creates a new method instead
If you misspell the method name (e.g., tostring() instead of toString()) without @Override, Java creates a brand new method. Your override never happens โ€” the parent's version is still called. @Override would have caught this at compile time.

๐Ÿ” Covariant Return Type

Since Java 5, an overriding method can return a subtype of the parent method's return type. This is called a covariant return type.

COVARIANT RETURN TYPE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Animal { Animal create() { return new Animal(); } } class Dog extends Animal { @Override Dog create() { return new Dog(); } // Dog IS-A Animal โ†’ valid! } // Parent returns Animal, child returns Dog (a subtype) โ€” OK

๐Ÿ’ป Program 1 โ€” Method Overriding & Runtime Polymorphism

MethodOverriding.java
class Shape {
    String color;

    Shape(String color) { this.color = color; }

    double area() {
        return 0;  // default โ€” overridden by each shape
    }

    void describe() {
        System.out.printf("%-12s | color: %-6s | area: %.2f%n",
            getClass().getSimpleName(), color, area());
    }
}

class Circle extends Shape {
    double radius;
    Circle(String color, double radius) {
        super(color); this.radius = radius;
    }
    @Override
    double area() { return Math.PI * radius * radius; }
}

class Rectangle extends Shape {
    double width, height;
    Rectangle(String color, double w, double h) {
        super(color); width = w; height = h;
    }
    @Override
    double area() { return width * height; }
}

class Triangle extends Shape {
    double base, heightT;
    Triangle(String color, double b, double h) {
        super(color); base = b; heightT = h;
    }
    @Override
    double area() { return 0.5 * base * heightT; }
}

public class MethodOverriding {
    public static void main(String[] args) {
        // Parent reference array โ€” polymorphic behaviour
        Shape[] shapes = {
            new Circle("Red",    5),
            new Rectangle("Blue",  4, 6),
            new Triangle("Green", 3, 8),
            new Shape("None")     // base shape โ€” area 0
        };
        for (Shape s : shapes)
            s.describe();   // calls each class's area() at runtime
    }
}
โ–ถ OUTPUT
Circle | color: Red | area: 78.54 Rectangle | color: Blue | area: 24.00 Triangle | color: Green | area: 12.00 Shape | color: None | area: 0.00

๐Ÿ’ป Program 2 โ€” Access Modifier Widening & Covariant Return

OverrideRules.java
class Vehicle {
    // protected โ†’ child can widen to public, cannot narrow to private
    protected Vehicle getInstance() {
        System.out.println("Vehicle.getInstance()");
        return new Vehicle();
    }

    protected void fuel() {
        System.out.println("Vehicle uses fuel");
    }
}

class Car extends Vehicle {

    @Override
    public Car getInstance() {          // โœ… wider (public) + covariant (Car)
        System.out.println("Car.getInstance() โ€” covariant return");
        return new Car();
    }

    @Override
    public void fuel() {                 // โœ… widened: protected โ†’ public
        System.out.println("Car uses petrol");
    }

    // โŒ would be compile error:
    // @Override private void fuel() { }   narrowing public โ†’ private
}

public class OverrideRules {
    public static void main(String[] args) {
        Vehicle v = new Car();    // parent ref, child object
        v.getInstance();           // Car's version runs โ€” runtime dispatch
        v.fuel();                   // Car's version runs

        Car c = new Car();
        Car ref = c.getInstance();  // covariant: returns Car directly (no cast)
        ref.fuel();
    }
}
โ–ถ OUTPUT
Car.getInstance() โ€” covariant return Car uses petrol Car.getInstance() โ€” covariant return Car uses petrol

๐Ÿ’ป Program 3 โ€” Method Hiding (static) vs Overriding

HidingVsOverriding.java
class Parent {
    static void staticMethod() {
        System.out.println("Parent - static");
    }
    void instanceMethod() {
        System.out.println("Parent - instance");
    }
}

class Child extends Parent {
    static void staticMethod() {     // METHOD HIDING โ€” not overriding
        System.out.println("Child - static");
    }
    @Override
    void instanceMethod() {         // TRUE OVERRIDING
        System.out.println("Child - instance");
    }
}

public class HidingVsOverriding {
    public static void main(String[] args) {
        Parent p = new Child();

        // Static โ€” resolved by REFERENCE TYPE (compile time)
        p.staticMethod();    // "Parent - static"  โ† reference is Parent

        // Instance โ€” resolved by OBJECT TYPE (runtime)
        p.instanceMethod();  // "Child - instance" โ† object is Child
    }
}
โ–ถ OUTPUT
Parent - static Child - instance
โš ๏ธ Static methods are hidden, not overridden โ€” no polymorphism
Static methods belong to the class, not to any object. When you call a static method via a reference variable, Java uses the reference type (compile-time type) to resolve it โ€” not the actual object type. This is method hiding, not overriding, and it does NOT participate in runtime polymorphism. @Override on a static method causes a compile error.
๐Ÿ’ก Always use @Override โ€” it costs nothing and saves hours
@Override is free insurance. It catches: misspelled method names, wrong parameter types, trying to override a final or static method, and trying to override when there is no parent method to override. Make it a habit to always add it when intending to override.
โญ Must-Know Interview Topics
Overriding rules (name/params/return/access/exceptions) ยท what cannot be overridden ยท @Override purpose ยท method hiding vs overriding ยท covariant return ยท overriding vs overloading ยท runtime polymorphism mechanism
Q1. What are the rules for method overriding in Java?
1. Same method name โ€” exact match
2. Same parameter list โ€” same types, count, and order (different params = overloading)
3. Same or covariant return type โ€” return type must be the same or a subtype of the parent's return type
4. Access modifier must be same or wider โ€” can widen (protected โ†’ public) but cannot narrow (public โ†’ private/protected)
5. Checked exceptions must be same or narrower โ€” cannot declare new or broader checked exceptions
6. Inheritance required โ€” can only override a method from a superclass
Q2. What methods cannot be overridden?
final methods โ€” compiler error if you try; sealed by the parent
static methods โ€” they are hidden, not overridden; resolved by reference type, not object type
private methods โ€” not inherited at all; a child defining the same name creates a brand new independent method
Constructors โ€” constructors are never inherited and cannot be overridden
Q3. What is the purpose of @Override annotation?
@Override tells the compiler: "I intend this to override a parent method." The compiler then verifies the contract:
โ€” If no matching parent method exists โ†’ compile error (catches typos, wrong params)
โ€” If trying to override a final or static method โ†’ compile error

Without @Override, a typo like tostring() instead of toString() silently creates a new independent method. The parent's toString() is never overridden and incorrect output appears at runtime with no error. @Override is optional but universally considered mandatory in professional code.
Q4. What is the difference between method overriding and method hiding?
Method overriding (instance methods): the child's version replaces the parent's at runtime. Which version runs is determined by the actual object type โ€” this is runtime polymorphism / dynamic dispatch.

Method hiding (static methods): defining a static method in a child with the same name as a parent static method does not override it. Which version runs is determined by the reference type at compile time. No runtime polymorphism occurs. A Parent reference always calls the Parent static method even if the object is a Child.
Q5. What is a covariant return type?
A covariant return type means the overriding method can return a subtype of the parent method's declared return type.

Example: if the parent declares Animal getInstance(), the child can override it as Dog getInstance() since Dog is a subtype of Animal. This is valid since Java 5.

Benefit: callers using a Dog reference get back a Dog directly without casting; callers using an Animal reference still work because a Dog IS-A Animal. Factory and builder patterns use this extensively.
Q6. Can you override a method and make it throw a broader checked exception?
No โ€” the overriding method cannot throw new or broader checked exceptions than the parent.

If the parent declares void read() throws IOException, the child can:
โ€” throw FileNotFoundException (subclass of IOException โ€” narrower โœ…)
โ€” throw nothing at all โœ…
โ€” throw Exception (superclass of IOException โ€” broader โŒ โ€” compile error)

Reason: code holding a Parent reference only catches IOException. If the child threw Exception, that catch block would miss it โ€” violating the Liskov Substitution Principle.
Note: unchecked exceptions (RuntimeException and subclasses) have no such restriction โ€” they can be freely added or broadened in overrides.
Q7. How does the JVM decide which overridden method to call?
Through Dynamic Method Dispatch (also called virtual method invocation). At runtime, the JVM looks at the actual type of the object on the heap, not the type of the reference variable, to determine which method to call.

Mechanism: each object carries a reference to its class's virtual method table (vtable), which maps method names to their most-derived implementation. When a method is called via a reference, the JVM follows the reference โ†’ object โ†’ vtable โ†’ most specific override.

This is why: Animal a = new Dog(); a.sound(); calls Dog.sound() โ€” the vtable for the Dog object points to Dog.sound().

๐ŸŒฑ Method Overriding in Spring Boot

Overriding is fundamental to Spring Boot โ€” the framework is designed around polymorphism. You override methods to customise framework behaviour, implement interfaces, and extend base classes.

โœ… Common overriding patterns in Spring Boot
toString() / equals() / hashCode() โ€” overriding Object methods on entities for logging and JPA identity
UserDetailsService.loadUserByUsername() โ€” override to provide custom user lookup for Spring Security
WebSecurityConfigurerAdapter methods โ€” override configure(HttpSecurity) to define security rules (legacy)
JpaRepository custom query methods โ€” override default save/delete behaviour in repository
AbstractHealthIndicator.doHealthCheck() โ€” override to add custom health checks to Actuator
CustomUserDetailsService.java + User.java โ€” Spring Boot overriding
// โ”€โ”€ Override loadUserByUsername for Spring Security โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@Service
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepo;

    public CustomUserDetailsService(UserRepository userRepo) {
        this.userRepo = userRepo;
    }

    @Override          // implements UserDetailsService contract
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        return userRepo.findByEmail(username)
            .orElseThrow(() ->
                new UsernameNotFoundException("User not found: " + username));
    }
}

// โ”€โ”€ Override toString/equals/hashCode on JPA entity โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@Entity
public class User {
    @Id @GeneratedValue
    private Long id;
    private String email;

    @Override
    public String toString() {
        return "User{id=" + id + ", email='" + email + "'}";
    }

    @Override   // JPA entities should use id for equality
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        return id != null && id.equals(((User) o).id);
    }

    @Override
    public int hashCode() {
        return getClass().hashCode();
    }
}
๐Ÿ“Œ Why JPA entities need special equals/hashCode
Default Object.equals() uses memory address โ€” two entities loaded at different times representing the same DB row would be unequal. Hibernate collections and caching rely on equals()/hashCode() for identity. Best practice: base equality on the database ID, and use getClass().hashCode() as a stable hash that works even before the ID is set.
โญ Must-Know Exam Points
  • Overriding: same name + same params + same/covariant return + same/wider access
  • Cannot override: final, static, private methods, constructors
  • Static methods are hidden (compile-time), not overridden (runtime)
  • @Override โ€” optional but causes compile error if no parent method matches
  • Access modifier: can widen (protectedโ†’public), cannot narrow (publicโ†’private)
  • Checked exceptions: can throw fewer/narrower, not more/broader
  • Covariant return type: child can return a subtype (since Java 5)
  • Runtime: resolved by object type; Static: resolved by reference type

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Can you override a private method?NoPrivate methods aren't inherited โ€” child defines a new independent method
Can you override a static method?No โ€” it is hiddenStatic methods use compile-time (reference type) dispatch, not runtime dispatch
Can overriding method narrow access (publicโ†’private)?No โ€” compile errorMust be same or wider access modifier
Parent: void m() throws IOException. Child can throwโ€ฆ?Nothing, or FileNotFoundExceptionCannot throw Exception (broader) โ€” compile error
Animal a = new Dog(); a.sound() callsโ€ฆ?Dog's sound()Runtime dispatch โ€” object type (Dog) determines method
Parent a = new Child(); a.staticMethod() callsโ€ฆ?Parent's staticMethod()Static uses reference type โ€” method hiding, not polymorphism
Is @Override mandatory for overriding?No โ€” but strongly recommendedOptional, but without it typos create new methods silently

๐Ÿ“– One-Liner Definitions

  • Method overriding โ€” A subclass providing its own implementation of an inherited method with the same signature; resolved at runtime.
  • Dynamic method dispatch โ€” The JVM mechanism that resolves an overridden method call based on the actual runtime type of the object, not the reference type.
  • Method hiding โ€” Redefining a static method in a subclass; resolved at compile time by reference type โ€” not polymorphism.
  • Covariant return type โ€” An overriding method returning a subtype of the parent method's return type; valid since Java 5.
  • @Override โ€” Annotation that instructs the compiler to verify a method truly overrides a parent method; causes a compile error if the signature doesn't match.
TOPIC 17

Packages

๐Ÿ“ฆ What is a Package?

A package is a namespace that groups related classes, interfaces, enums, and annotations together. On the file system, a package maps directly to a folder/directory. Package names follow the reverse domain name convention to guarantee global uniqueness.

๐Ÿ“Œ Two purposes of packages
1. Namespace management โ€” two classes with the same name can coexist in different packages: com.app.util.Date vs java.util.Date
2. Access control โ€” the default (package-private) access modifier restricts visibility to classes in the same package; packages form the boundary for that access level

๐Ÿ“‚ Package โ†’ Folder Mapping

PACKAGE NAME TO DIRECTORY STRUCTURE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Package declaration: com.example.service File system: src/ โ””โ”€โ”€ com/ โ””โ”€โ”€ example/ โ””โ”€โ”€ service/ โ””โ”€โ”€ OrderService.java First line of OrderService.java: package com.example.service; Rules: โ†’ Package name must match the directory path exactly โ†’ All lowercase, dot-separated โ†’ Reverse domain: com.google.*, org.apache.*, io.github.* โ†’ No hyphens (invalid Java identifier); underscores allowed but rare โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

๐Ÿ”‘ The package Statement

The package statement must be the very first non-comment line in a Java source file. Only one package statement is allowed per file.

PACKAGE STATEMENT POSITION โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ // optional: comments and blank lines allowed here package com.example.service; โ† MUST be first statement import java.util.List; โ† imports come after package import java.util.ArrayList; public class OrderService { } โ† class definition last โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ If no package statement โ†’ class belongs to the "default package" (unnamed package). Avoid for anything beyond quick demos.

๐Ÿ“ฅ The import Statement

To use a class from another package you either use its fully qualified name every time, or add an import statement so you can use the simple name.

IMPORT TYPES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. Single-type import (preferred): import java.util.ArrayList; import java.util.List; // uses only what you name โ€” clear and explicit 2. Wildcard import (use with care): import java.util.*; // imports ALL public types from java.util // does NOT import sub-packages (java.util.concurrent.* is separate) // does NOT slow compilation โ€” only resolution at compile time 3. Static import (for static members): import static java.lang.Math.PI; import static java.lang.Math.*; // allows: area = PI * r * r instead of Math.PI * r * r 4. No import needed: โ†’ java.lang.* (String, System, Math, Object...) โ€” auto-imported โ†’ Classes in the same package โ€” no import needed โ†’ Fully qualified names: java.util.ArrayList list = new java.util.ArrayList(); โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

๐Ÿ“š Built-In Java Packages

PackageKey Contents
java.langString, Math, Object, System, Integer, Thread โ€” auto-imported
java.utilArrayList, HashMap, List, Collections, Arrays, Scanner, Date
java.ioFile, InputStream, OutputStream, BufferedReader, FileWriter
java.nioPath, Files, ByteBuffer โ€” modern I/O
java.netURL, HttpURLConnection, Socket
java.sqlConnection, Statement, ResultSet, DriverManager
java.timeLocalDate, LocalDateTime, Duration, ZonedDateTime
java.util.concurrentExecutorService, Future, ConcurrentHashMap, AtomicInteger
๐Ÿ’ก Wildcard import does NOT import sub-packages
import java.util.* gives you ArrayList, HashMap, etc. โ€” but not java.util.concurrent.ConcurrentHashMap. You need a separate import java.util.concurrent.* for that. Each package level requires its own import.

๐Ÿ’ป Program 1 โ€” Declaring & Using a Package

com/example/model/Student.java
// File must be at: src/com/example/model/Student.java
package com.example.model;           // first non-comment statement

public class Student {
    private String name;
    private int    age;

    public Student(String name, int age) {
        this.name = name;
        this.age  = age;
    }
    public  String getName() { return name; }
    public     int getAge()  { return age;  }

    @Override
    public String toString() {
        return "Student{" + name + ", age=" + age + "}";
    }
}
com/example/service/StudentService.java
package com.example.service;

import com.example.model.Student;   // explicit single-type import
import java.util.ArrayList;
import java.util.List;

public class StudentService {
    private List<Student> students = new ArrayList<>();

    public void addStudent(Student s) {
        students.add(s);
    }

    public void printAll() {
        students.forEach(s ->
            System.out.println("  " + s));
    }
}
com/example/Main.java
package com.example;

import com.example.model.Student;
import com.example.service.StudentService;

public class Main {
    public static void main(String[] args) {
        StudentService svc = new StudentService();
        svc.addStudent(new Student("Rahul", 20));
        svc.addStudent(new Student("Priya", 22));
        svc.addStudent(new Student("Arjun", 21));
        System.out.println("All students:");
        svc.printAll();
    }
}
โ–ถ OUTPUT
All students: Student{Rahul, age=20} Student{Priya, age=22} Student{Arjun, age=21}

๐Ÿ’ป Program 2 โ€” Static Import & Fully Qualified Name

ImportDemo.java
package com.example;

// Static import โ€” use Math constants/methods without "Math."
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
import static java.lang.Math.pow;

public class ImportDemo {
    public static void main(String[] args) {

        // Static import: use PI and sqrt directly
        double radius = 7;
        double area   = PI * radius * radius;
        double hyp    = sqrt(pow(3,2) + pow(4,2));  // = 5.0

        System.out.printf("Circle area (r=7): %.4f%n", area);
        System.out.printf("Hypotenuse (3,4) : %.1f%n",  hyp);

        // Fully qualified name โ€” no import needed but verbose
        java.util.Date today = new java.util.Date();
        System.out.println("Today: " + today.toString().substring(0,10));
    }
}
โ–ถ OUTPUT
Circle area (r=7): 153.9380 Hypotenuse (3,4) : 5.0 Today: Wed Mar 11

๐Ÿ’ป Program 3 โ€” Name Clash: Resolving with Fully Qualified Names

NameClash.java โ€” two Date classes
package com.example;

// Import one โ€” the other must use fully qualified name
import java.sql.Date;   // sql.Date imported by simple name

public class NameClash {
    public static void main(String[] args) {

        // java.sql.Date โ€” uses simple name (imported)
        Date sqlDate = new Date(System.currentTimeMillis());

        // java.util.Date โ€” must use fully qualified name to avoid clash
        java.util.Date utilDate = new java.util.Date();

        System.out.println("sql.Date  : " + sqlDate);
        System.out.println("util.Date : " + utilDate);
        System.out.println("Same name, different packages โ€” no conflict!");
    }
}
โ–ถ OUTPUT
sql.Date : 2026-03-11 util.Date : Wed Mar 11 16:40:43 IST 2026 Same name, different packages โ€” no conflict!
โš ๏ธ Wildcard import cannot resolve name clashes
If you write import java.util.*; and import java.sql.*;, both packages have a Date class. Any use of Date becomes ambiguous โ€” compile error. The fix: import one by its simple name and use the fully qualified name for the other, as shown above.
๐Ÿ’ก java.lang is the only auto-imported package
java.lang contains String, System, Math, Object, Integer, Thread, etc. It is automatically imported in every Java file โ€” you never write import java.lang.*. Every other package (including java.util) requires an explicit import.
โญ Must-Know Interview Topics
Package purpose (namespace + access) ยท package statement position ยท import types (single/wildcard/static) ยท java.lang auto-import ยท wildcard clash ยท fully qualified name ยท default package ยท reverse domain naming
Q1. What is a package and why is it used?
A package is a namespace that groups related Java types together, corresponding to a directory on the file system.

Two key purposes:
1. Namespace management: prevents naming collisions โ€” two classes named Date can coexist in java.util and java.sql without conflict
2. Access control: the default (package-private) access modifier makes members visible only within the same package, providing encapsulation at the package level
Q2. Where must the package statement appear in a file?
The package statement must be the first non-comment line in the source file. Only one package statement is allowed per file. It must appear before any import statements and before the class declaration.

If there is no package statement, the class belongs to the default (unnamed) package. Classes in the default package cannot be imported by classes in named packages โ€” so the default package is only suitable for quick experiments and single-file demos.
Q3. What is the difference between single-type import and wildcard import?
Single-type import (import java.util.ArrayList): imports exactly one class. Explicit and clear โ€” anyone reading the file immediately sees which classes are used from which packages.

Wildcard import (import java.util.*): imports all public types in a package. Does not slow down compilation (the compiler loads only what is actually used). Does not import sub-packages. Can cause ambiguity if two packages contain a class with the same name โ€” compile error.

Professional preference: single-type imports, enforced by Checkstyle and most IDE code style settings.
Q4. Which package is automatically imported in every Java file?
java.lang is the only package that is automatically imported in every Java source file without any explicit import statement. It contains the most fundamental classes: String, Object, System, Math, Integer, Long, Thread, Runnable, Exception, etc.

Every other package โ€” including java.util โ€” requires an explicit import statement.
Q5. What is a static import? When should it be used?
import static imports a static member (field or method) of a class, allowing it to be used without the class name prefix.

Example: import static java.lang.Math.PI; allows writing PI instead of Math.PI.

When to use: in test code (import static org.junit.Assert.* is idiomatic), and for frequently used constants/utility methods where the class prefix adds no clarity. When to avoid: in production code where it can obscure which class a method comes from โ€” Math.sqrt() is clearer than a bare sqrt().
Q6. What is the default package and what are its limitations?
The default package is the unnamed package that a class belongs to when it has no package statement.

Limitations:
1. Classes in the default package cannot be imported by classes in named packages โ€” only other default-package classes can use them
2. No way to have two classes with the same name (no namespace separation)
3. Default package classes cannot use protected package-access across sub-paths

Use only for: quick experiments, single-file compilation demos, and practice programs. Never use for any real project.
Q7. Why does Java use reverse domain names for packages?
Reverse domain naming (e.g., com.google.gson, org.apache.commons) guarantees global uniqueness of package names.

Since domain names are unique by definition (enforced by internet registrars), reversing them creates a namespace where no two organisations should produce the same package prefix โ€” unless they control the same domain. This convention prevents collisions between libraries from different vendors even when they contain classes with identical names.

๐ŸŒฑ Packages in Spring Boot

Spring Boot relies heavily on its own package scanning to auto-discover beans. Understanding the package structure is critical to making component scanning work correctly.

โœ… Standard Spring Boot package layout
Root package: contains @SpringBootApplication โ€” scanning starts here
controller/: @RestController, @Controller classes
service/: @Service classes
repository/: @Repository, JPA repository interfaces
model/ or entity/: @Entity JPA classes
dto/: Data Transfer Objects
config/: @Configuration classes
exception/: custom exception classes
Spring Boot component scanning โ€” package rules
// com/example/ecommerce/EcommerceApplication.java
package com.example.ecommerce;    // โ† ROOT package

@SpringBootApplication           // scans com.example.ecommerce and ALL sub-packages
public class EcommerceApplication {
    public static void main(String[] args) {
        SpringApplication.run(EcommerceApplication.class, args);
    }
}

// โœ… Discovered automatically (sub-package of root):
// package com.example.ecommerce.service;  โ†’ @Service beans found
// package com.example.ecommerce.controller; โ†’ @RestController found

// โŒ NOT discovered (different root โ€” outside scan range):
// package com.other.service;  โ†’ beans here are NOT registered!

// To scan additional packages explicitly:
@SpringBootApplication(scanBasePackages =
    {"com.example.ecommerce", "com.shared.utils"})
public class EcommerceApplication { ... }
โš ๏ธ @SpringBootApplication must be in the root package
If the main class is placed in the default package or in a package that is a parent of your controllers/services, Spring component scanning will either not find your beans or scan too broadly (the entire classpath). Always place the @SpringBootApplication class in the topmost package of your application, and let all other packages be sub-packages of it.
โญ Must-Know Exam Points
  • package statement = first non-comment line in the file
  • Package maps to a directory/folder on the file system
  • java.lang = only auto-imported package
  • Wildcard import (*) does NOT import sub-packages
  • Name clash between two wildcard imports โ†’ compile error
  • Fix for name clash: import one by simple name, use fully qualified name for the other
  • Default package: no package statement โ†’ cannot be imported by named packages
  • Static import: import static ClassName.member โ€” allows using static members without class prefix
  • Reverse domain naming โ†’ guarantees global uniqueness

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Which package is auto-imported?java.lang onlyjava.util, java.io etc. all need explicit import
Does import java.util.* import java.util.concurrent?NoWildcard doesn't cross into sub-packages
Two wildcard imports with same class name?Compile errorAmbiguous reference โ€” must use fully qualified name
Can a default-package class be imported?NoNamed packages cannot import default-package classes
Where must package statement appear?First non-comment lineBefore imports and class declaration
Does wildcard import slow compilation?NoCompiler only loads actually used types โ€” wildcard is just a convenience
What does static import do?Imports a static field/methodAllows use without class prefix; import static Math.PI โ†’ use PI directly

๐Ÿ“– One-Liner Definitions

  • Package โ€” A namespace grouping related Java types, mapping to a directory; declared with the package keyword as the first statement.
  • Import โ€” A directive that allows using a class by its simple name rather than its fully qualified name.
  • Wildcard import โ€” import pkg.*; imports all public types from a single package level; does not include sub-packages.
  • Static import โ€” import static pkg.Class.member; allows using a static field or method without the class name prefix.
  • Fully qualified name โ€” The complete package + class name: java.util.ArrayList; always unique and never requires an import.
  • Default package โ€” The unnamed package for classes with no package statement; cannot be imported by named packages.
  • Reverse domain naming โ€” Convention of using reversed internet domain as package root (e.g., com.google.*) to ensure global uniqueness.
TOPIC 18

Access Modifiers

๐Ÿ” What are Access Modifiers?

Access modifiers control the visibility and accessibility of classes, fields, methods, and constructors. Java has four access levels โ€” from most to least restrictive: private, default (no keyword), protected, and public.

๐Ÿ“Œ Access modifiers are the enforcement mechanism of Encapsulation
Encapsulation (Topic 7) is the principle โ€” hide internal data, expose controlled interface. Access modifiers are the language tool that enforces that principle. Without access modifiers every field and method would be globally accessible โ€” encapsulation would be impossible.

๐Ÿ“Š The Access Modifier Table

ACCESS LEVELS โ€” WHO CAN SEE WHAT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ Same โ”‚ Same โ”‚ Sub- โ”‚ Other Modifier โ”‚ Class โ”‚ Package โ”‚ class โ”‚ Package โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ private โ”‚ โœ… โ”‚ โŒ โ”‚ โŒ โ”‚ โŒ default (none) โ”‚ โœ… โ”‚ โœ… โ”‚ โŒ โ”‚ โŒ protected โ”‚ โœ… โ”‚ โœ… โ”‚ โœ… โ”‚ โŒ public โ”‚ โœ… โ”‚ โœ… โ”‚ โœ… โ”‚ โœ… โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Memory aid (most โ†’ least restrictive): private < default < protected < public

๐Ÿ” Each Modifier Explained

1. private

Accessible only within the same class. The most restrictive level. Use for all instance variables (the cornerstone of encapsulation) and for helper methods that are implementation details not meant for external callers.

PRIVATE โ€” same class only class BankAccount { private double balance; // only BankAccount can touch this private void recalculate() {} // internal helper } class Main { BankAccount a = new BankAccount(); a.balance = 1000; // โŒ compile error โ€” private!

2. default (package-private)

No keyword is written. Accessible to all classes in the same package. Classes in other packages (even subclasses in a different package) cannot access it. Used when you want package-internal helpers but not wider exposure.

DEFAULT โ€” same package only (no keyword written) package com.example.service; class Helper { // default class โ€” not public! void assist() {} // default method } // com.example.service.SomeClass โ†’ can use Helper โœ… // com.example.other.OtherClass โ†’ cannot see Helper โŒ

3. protected

Accessible within the same package AND by subclasses in any package. The key nuance: a subclass in a different package can access protected members only through its own reference or subclass reference, not through a parent class reference.

PROTECTED โ€” same package + subclasses anywhere package com.animals; class Animal { protected String name; // accessible to subclasses everywhere protected void breathe() {} } package com.pets; // different package! import com.animals.Animal; class Dog extends Animal { void show() { System.out.println(name); // โœ… own inherited field breathe(); // โœ… inherited method } }

4. public

Accessible from everywhere โ€” any class in any package. Use for the intentional public API of your class: constructors you want others to call, methods you want others to use, and constants you want to share.

๐Ÿ›๏ธ Modifiers for Top-Level Classes

TOP-LEVEL CLASS ACCESS RULES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Top-level classes (not nested) can only be: โœ… public โ†’ accessible from anywhere โœ… default โ†’ accessible within the same package only โŒ private class MyClass {} โ†’ compile error โŒ protected class MyClass {} โ†’ compile error One public class per .java file, and it must match the filename: File: OrderService.java โ†’ public class OrderService { } A file CAN have multiple classes, but only ONE can be public.

๐Ÿ“ protected โ€” The Tricky One

PROTECTED SUBCLASS ACCESS โ€” THE NUANCE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ package com.base; class Parent { protected int value = 42; } package com.child; class Child extends Parent { void test() { System.out.println(value); // โœ… own inherited field Child c2 = new Child(); System.out.println(c2.value); // โœ… same class (Child) Parent p = new Parent(); System.out.println(p.value); // โŒ compile error! // accessing protected via PARENT reference from different package // is NOT allowed โ€” even in a subclass } } โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Rule: In a different package, protected access is only valid through the subclass type โ€” not through a parent type reference.
๐Ÿ’ก Best Practice Summary
Fields: always private โ€” expose via getters/setters
Helper methods: private โ€” not part of the public contract
Methods for subclasses to override/use: protected
Public API methods: public
Package utilities: default (no modifier)
Rule of thumb: use the most restrictive modifier that still allows the class to function correctly.

๐Ÿ’ป Program 1 โ€” All Four Modifiers in One Class

AccessDemo.java โ€” all four modifiers
package com.example;

public class Employee {

    public    String companyName = "TechCorp";  // anyone
    protected String department  = "Engineering"; // package + subclasses
              String teamCode    = "T42";   // package only (default)
    private   String salary      = "CONFIDENTIAL"; // this class only

    public    void introduce() {
        // can access ALL four from within the class
        System.out.println("Company : " + companyName);
        System.out.println("Dept    : " + department);
        System.out.println("Team    : " + teamCode);
        System.out.println("Salary  : " + salary);
    }

    public    void publicMethod()    { System.out.println("public method");    }
    protected void protectedMethod() { System.out.println("protected method"); }
              void defaultMethod()    { System.out.println("default method");    }
    private   void privateMethod()    { System.out.println("private method");    }
}

// Same package โ€” can access public, protected, default; NOT private
class SamePackageTest {
    public static void main(String[] args) {
        Employee e = new Employee();
        e.introduce();       // public โ€” OK
        e.publicMethod();
        e.protectedMethod(); // same package โ€” OK
        e.defaultMethod();   // same package โ€” OK
        // e.privateMethod();  โ† compile error
        System.out.println(e.companyName);  // public โ€” OK
        System.out.println(e.teamCode);     // default โ€” OK (same pkg)
        // System.out.println(e.salary);  โ† compile error
    }
}
โ–ถ OUTPUT
Company : TechCorp Dept : Engineering Team : T42 Salary : CONFIDENTIAL public method protected method default method

๐Ÿ’ป Program 2 โ€” protected in Subclass (Different Package)

com/animals/Animal.java + com/pets/Dog.java
// โ”€โ”€ com/animals/Animal.java โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
package com.animals;
public class Animal {
    protected String name;
    protected int    age;

    public Animal(String name, int age) {
        this.name = name;
        this.age  = age;
    }
    protected void breathe() {
        System.out.println(name + " is breathing");
    }
}

// โ”€โ”€ com/pets/Dog.java โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
package com.pets;
import com.animals.Animal;

public class Dog extends Animal {
    String breed;

    public Dog(String name, int age, String breed) {
        super(name, age);    // calls Animal constructor
        this.breed = breed;
    }

    public void describe() {
        // protected fields accessible via inheritance in subclass
        System.out.printf("%s | age:%d | breed:%s%n",
            name, age, breed);
        breathe();           // protected method โ€” accessible in subclass

        // โŒ Would be compile error (different pkg, parent ref):
        // Animal a = new Animal("Cat",1); a.name โ†’ not accessible
    }

    public static void main(String[] args) {
        new Dog("Bruno", 3, "Labrador").describe();
    }
}
โ–ถ OUTPUT
Bruno | age:3 | breed:Labrador Bruno is breathing

๐Ÿ’ป Program 3 โ€” Access Modifier on Constructor (Singleton + Factory)

ConstructorAccess.java
// Private constructor = Singleton (Topic 11 revisited)
class Config {
    private static final Config INSTANCE = new Config();
    private Config() {}                  // private constructor
    public static Config getInstance() { return INSTANCE; }
    public String getDbUrl() { return "jdbc:mysql://localhost/db"; }
}

// Protected constructor โ€” only subclasses can call new
class AbstractShape {
    String color;
    protected AbstractShape(String color) {   // protected constructor
        this.color = color;
    }
    double area() { return 0; }
}

class Square extends AbstractShape {
    double side;
    public Square(String color, double side) {
        super(color);     // can call protected parent constructor
        this.side = side;
    }
    @Override double area() { return side * side; }
}

public class ConstructorAccess {
    public static void main(String[] args) {
        Config cfg = Config.getInstance();
        System.out.println("DB: " + cfg.getDbUrl());
        // new Config();  โ† compile error โ€” private constructor

        Square sq = new Square("Blue", 5);
        System.out.printf("Square area: %.1f | color: %s%n",
            sq.area(), sq.color);
        // new AbstractShape("Red");  โ† compile error (same pkg ok, diff pkg no)
    }
}
โ–ถ OUTPUT
DB: jdbc:mysql://localhost/db Square area: 25.0 | color: Blue
โš ๏ธ protected โ‰  "almost public"
A common misconception is that protected is nearly as open as public. In practice, the sub-package nuance makes it tricky: a subclass in a different package can access protected members only via its own inherited reference โ€” not via a Parent type reference. This is frequently tested in exams and catches even experienced developers.
๐Ÿ’ก Access modifier checklist for a new class
1. All fields โ†’ private (always)
2. Getters/setters โ†’ public (for external consumers) or protected (for subclasses only)
3. Template/hook methods for subclasses โ†’ protected
4. Internal utility methods โ†’ private
5. Package-scoped helpers โ†’ default (no modifier)
6. Class itself โ†’ public if used outside the package, else default
โญ Must-Know Interview Topics
Four modifiers and their scope ยท access table ยท top-level class modifiers (public/default only) ยท protected nuance in different packages ยท override rule (same or wider) ยท private constructor use cases
Q1. What are the four access modifiers in Java and their visibility?
private: visible only within the same class
default (no keyword): visible to all classes in the same package
protected: visible to all classes in the same package, AND to subclasses in any package
public: visible everywhere โ€” any class in any package

Order from most to least restrictive: private < default < protected < public
Q2. What access modifiers can be applied to a top-level class?
A top-level class (not nested) can only have two access modifiers:
public โ€” accessible from any package
default (no keyword) โ€” accessible only within the same package

private and protected are not valid for top-level classes โ€” they cause a compile error. Additionally, a file can contain only one public class, and its name must match the filename exactly.
Q3. What is the exact rule for protected access in a different package?
In a different package, a subclass can access a protected member only through its own reference or an instance of its own subclass type โ€” not through a reference of the parent class type.

Example: if Dog is in a different package from Animal and Animal.name is protected:
โ€” this.name inside Dog โ†’ โœ… own inherited field
โ€” new Dog().name inside Dog โ†’ โœ… same type
โ€” new Animal().name inside Dog โ†’ โŒ compile error (parent type reference, different package)
Q4. Why should instance variables always be private?
Making instance variables private enforces encapsulation:
1. Validation: setters can validate values before assignment (if (age < 0) throw)
2. Controlled mutation: read-only fields can have getters but no setters
3. Implementation freedom: internal representation can change without breaking callers (change int age to LocalDate birthDate and update the getter โ€” all callers still use getAge())
4. Thread safety: easier to synchronise access through controlled methods
5. Debugging: all mutations go through one point โ€” easy to add logging/breakpoints
Q5. Can access modifier be narrowed when overriding a method?
No โ€” narrowing is not allowed. When overriding, the access modifier must be the same or wider.

Valid: protected โ†’ public (widening โœ…)
Valid: public โ†’ public (same โœ…)
Invalid: public โ†’ protected (narrowing โŒ โ€” compile error)

Reason: callers using the parent reference expect the same or more accessible contract. If a child narrowed access, code that compiled against the parent type would break at runtime โ€” violating the Liskov Substitution Principle.
Q6. What is the difference between default and protected in terms of subclass access?
Default (package-private): accessible to all classes in the same package, but NOT accessible to subclasses in a different package. Even if Dog extends Animal and Animal has a default field, if Dog is in a different package it cannot access that field.

Protected: accessible to all classes in the same package AND to subclasses anywhere (including different packages), subject to the own-reference rule.

Summary: protected adds cross-package subclass access on top of what default provides.

๐ŸŒฑ Access Modifiers in Spring Boot

Spring Boot code follows strict access modifier conventions. Framework-specific annotations interact with access modifiers in important ways.

โœ… Access modifier conventions in Spring Boot
@Service / @Component / @Controller โ€” class must be public (Spring proxying requires it in most cases)
Injected fields โ€” keep private final; use constructor injection not field injection
@RequestMapping handler methods โ€” public (Spring MVC requires this)
@Bean methods in @Configuration โ€” public or protected
Repository interfaces โ€” public interface
Internal service helpers โ€” private methods within the service class
Template methods for subclasses โ€” protected in abstract base service
OrderService.java โ€” access modifier best practices in Spring
@Service
public class OrderService {                  // public class โ€” Spring proxy

    private final OrderRepository orderRepo;  // private final โ€” encapsulated
    private final PaymentService  paymentSvc;

    public OrderService(OrderRepository r, PaymentService p) {
        this.orderRepo  = r;
        this.paymentSvc = p;
    }

    public Order createOrder(CreateOrderRequest req) {   // public API
        validateRequest(req);        // calls private helper
        Order order = buildOrder(req);
        return orderRepo.save(order);
    }

    private void validateRequest(CreateOrderRequest req) { // private helper
        if (req == null)
            throw new IllegalArgumentException("Request must not be null");
    }

    private Order buildOrder(CreateOrderRequest req) {      // private builder
        return new Order(req.getProduct(), req.getQuantity());
    }
}
โš ๏ธ Spring proxying and access modifiers
Spring uses CGLIB or JDK dynamic proxies to implement AOP features like @Transactional and @Cacheable. These proxies wrap your bean. If the method you annotate is private, Spring cannot intercept it โ€” the annotation is silently ignored and no transaction/cache is applied. Always make @Transactional and @Cacheable methods public.
โญ Must-Know Exam Points
  • private โ†’ same class only
  • default โ†’ same package (no keyword written)
  • protected โ†’ same package + subclasses anywhere
  • public โ†’ everywhere
  • Top-level class can only be public or default โ€” never private/protected
  • One file โ†’ one public class, name must match filename
  • Override: access can be same or wider โ€” never narrower
  • protected + different package: only via own/subclass reference โ€” NOT via parent reference
  • Best practice: fields always private; expose via public getters

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Can a top-level class be private?No โ€” compile errorOnly public or default allowed for top-level classes
Default access = which packages?Same package onlyNOT accessible to subclasses in different packages
Protected subclass in different package accesses via parent ref?Compile errorMust access via own/subclass ref, not parent type ref
Can override narrow access from public to protected?No โ€” compile errorMust be same or wider access modifier
How many public classes in one .java file?One maximumMust match filename; multiple classes allowed but only one public
Can private methods be overridden?NoNot inherited โ€” child creates a new independent method
Which is more restrictive: default or protected?defaultdefault blocks cross-package subclasses; protected allows them

๐Ÿ“– One-Liner Definitions

  • private โ€” Most restrictive modifier; member accessible only within its declaring class.
  • default (package-private) โ€” No keyword; member accessible to all classes in the same package, and no others.
  • protected โ€” Member accessible within the same package and to subclasses in any package (via own reference).
  • public โ€” Least restrictive; member accessible from any class in any package.
  • Access widening in override โ€” The rule that an overriding method's access modifier must be equal to or wider than the parent method's modifier.
TOPIC 19

Polymorphism & Dynamic Method Dispatch

๐ŸŽญ What is Polymorphism?

Polymorphism (Greek: "many forms") means one interface, many implementations. In Java it means a single method call can behave differently depending on the actual object type at runtime. It is one of the four pillars of OOP and is made possible by inheritance and method overriding.

๐Ÿ“Œ Two types of polymorphism
Compile-time polymorphism (Static / Early binding) โ†’ achieved via method overloading. The compiler decides which method to call based on the method signature at compile time.

Runtime polymorphism (Dynamic / Late binding) โ†’ achieved via method overriding + parent reference pointing to child object. The JVM decides which method to call at runtime based on the actual object type.

๐Ÿ“ Compile-Time Polymorphism โ€” Method Overloading

COMPILE-TIME POLYMORPHISM (Overloading) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Calculator { int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } int add(int a, int b, int c){ return a+b+c; } } calc.add(2, 3) โ†’ compiler picks add(int, int) calc.add(2.0, 3.0) โ†’ compiler picks add(double, double) calc.add(1, 2, 3) โ†’ compiler picks add(int, int, int) Resolution at COMPILE TIME โ†’ "early binding" Reference type determines which method โ†’ no runtime surprise

๐Ÿ”„ Runtime Polymorphism โ€” Dynamic Method Dispatch

Dynamic Method Dispatch (DMD) is the mechanism by which Java selects the correct overridden method to call at runtime, based on the actual object type โ€” not the declared reference type.

RUNTIME POLYMORPHISM โ€” THE KEY PATTERN โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Parent reference โ†’ Child object โ†“ Method call resolved by actual object type at RUNTIME Animal a; // reference type: Animal a = new Dog(); // actual object: Dog a.sound(); // โ†’ calls Dog.sound() โ† DMD a = new Cat(); // reassign to Cat object a.sound(); // โ†’ calls Cat.sound() โ† DMD Same reference variable `a`, different objects, different behaviour! This is the power of polymorphism.

๐Ÿง  How Dynamic Method Dispatch Works (vtable)

JVM VTABLE MECHANISM โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Every class has a virtual method table (vtable): Animal vtable: Dog vtable: โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ sound() โ†’ A.soundโ”‚ โ”‚ sound() โ†’ D.sound โ”‚ โ† overridden โ”‚ eat() โ†’ A.eat โ”‚ โ”‚ eat() โ†’ A.eat โ”‚ โ† inherited โ”‚ toStringโ†’ O.toStrโ”‚ โ”‚ toStringโ†’ O.toStr โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ Animal a = new Dog(); a.sound() โ”‚ โ”œโ”€ compiler: "sound() is defined in Animal โ€” OK to compile" โ”‚ uses Animal reference type for type checking โ”‚ โ””โ”€ JVM at runtime: "actual object is Dog" "look up Dog.vtable[sound()]" "call Dog.sound()" The vtable lookup is what makes polymorphism work.

โšก What Participates in Runtime Polymorphism

Runtime Polymorphism?Reason
Instance methods (overridden)โœ… YESResolved via vtable at runtime
Static methodsโŒ NOResolved by reference type at compile time (method hiding)
Instance variables (fields)โŒ NOField access resolved by reference type, not object type
ConstructorsโŒ NOConstructors are not inherited or overridden
Private methodsโŒ NONot inherited โ€” no vtable entry in child
final methodsโŒ NOCannot be overridden โ€” single vtable entry

๐ŸŒŠ The Power: Polymorphic Arrays & Collections

POLYMORPHISM WITH ARRAYS โ€” process many types uniformly โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Shape[] shapes = { new Circle(5), new Rectangle(4, 6), new Triangle(3, 8) }; for (Shape s : shapes) s.area(); // each calls ITS OWN area() implementation // no if/else, no instanceof needed! This is the open/closed principle: โ†’ Open for extension (add new Shape subclasses freely) โ†’ Closed for modification (the loop never changes) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

๐Ÿ” Field Hiding โ€” The Non-Polymorphic Trap

INSTANCE VARIABLES DO NOT PARTICIPATE IN POLYMORPHISM โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Animal { String type = "Animal"; } class Dog extends Animal { String type = "Dog"; } Animal a = new Dog(); System.out.println(a.type); // prints "Animal" โ† reference type! // NOT "Dog" โ€” fields use early binding Dog d = new Dog(); System.out.println(d.type); // prints "Dog" Rule: field access is always resolved by the REFERENCE type, method calls are resolved by the OBJECT type (if overridden).

๐Ÿ’ป Program 1 โ€” Runtime Polymorphism: Shape Calculator

Polymorphism.java
abstract class Shape {
    String color;
    Shape(String color) { this.color = color; }
    abstract double area();              // must override
    abstract double perimeter();         // must override
    void printInfo() {                   // calls overridden methods via DMD
        System.out.printf("%-12s color:%-6s area:%7.2f  perim:%7.2f%n",
            getClass().getSimpleName(), color, area(), perimeter());
    }
}

class Circle extends Shape {
    double r;
    Circle(String c, double r) { super(c); this.r = r; }
    @Override double area()      { return Math.PI * r * r; }
    @Override double perimeter() { return 2 * Math.PI * r; }
}

class Rectangle extends Shape {
    double w, h;
    Rectangle(String c, double w, double h) {
        super(c); this.w = w; this.h = h;
    }
    @Override double area()      { return w * h; }
    @Override double perimeter() { return 2 * (w + h); }
}

class Triangle extends Shape {
    double a, b, c;
    Triangle(String col, double a, double b, double c) {
        super(col); this.a=a; this.b=b; this.c=c;
    }
    @Override double area() {
        double s = (a+b+c)/2;
        return Math.sqrt(s*(s-a)*(s-b)*(s-c));
    }
    @Override double perimeter() { return a+b+c; }
}

public class Polymorphism {
    static void printTotal(Shape[] shapes) {
        double total = 0;
        for (Shape s : shapes) {
            s.printInfo();   // DMD: each shape prints its own info
            total += s.area();
        }
        System.out.printf("Total area: %.2f%n", total);
    }

    public static void main(String[] args) {
        Shape[] shapes = {
            new Circle("Red",    5),
            new Rectangle("Blue",  4, 6),
            new Triangle("Green", 3, 4, 5)
        };
        printTotal(shapes);
    }
}
โ–ถ OUTPUT
Circle color:Red area: 78.54 perim: 31.42 Rectangle color:Blue area: 24.00 perim: 20.00 Triangle color:Green area: 6.00 perim: 12.00 Total area: 108.54

๐Ÿ’ป Program 2 โ€” Field Hiding vs Method Overriding

FieldVsMethod.java
class Base {
    String type = "Base";                          // instance field
    void   show() { System.out.println("Base.show()"); }
}

class Derived extends Base {
    String type = "Derived";                       // hides Base.type
    @Override
    void   show() { System.out.println("Derived.show()"); }
}

public class FieldVsMethod {
    public static void main(String[] args) {
        Base ref = new Derived();  // parent ref, child object

        // Method โ†’ RUNTIME resolution (actual object type)
        ref.show();                 // "Derived.show()" โ† DMD

        // Field โ†’ COMPILE-TIME resolution (reference type)
        System.out.println(ref.type);  // "Base" โ† reference type wins!

        // Cast to Derived to access Derived.type
        Derived d = (Derived) ref;
        System.out.println(d.type);     // "Derived"
    }
}
โ–ถ OUTPUT
Derived.show() Base Derived

๐Ÿ’ป Program 3 โ€” Compile-Time Polymorphism: Overloading Resolution

CompileTimePolymorphism.java
public class Printer {

    void print(int n) {
        System.out.println("int:    " + n);
    }
    void print(double d) {
        System.out.println("double: " + d);
    }
    void print(String s) {
        System.out.println("String: " + s);
    }
    void print(int a, int b) {
        System.out.println("int,int: " + a + "," + b);
    }

    public static void main(String[] args) {
        Printer p = new Printer();
        p.print(42);         // โ†’ print(int)
        p.print(3.14);       // โ†’ print(double)
        p.print("Hello");    // โ†’ print(String)
        p.print(10, 20);     // โ†’ print(int, int)
        p.print('A');        // โ†’ print(int) โ€” char promoted to int!
    }
}
โ–ถ OUTPUT
int: 42 double: 3.14 String: Hello int,int: 10,20 int: 65
โš ๏ธ Fields are NOT polymorphic โ€” a common exam trap
When you access a field via a parent reference pointing to a child object, you get the parent's field value โ€” not the child's. Field access is resolved at compile time by the reference type. Only method calls go through dynamic dispatch. This is why exposing fields directly (without getters) breaks polymorphism.
๐Ÿ’ก The real-world power: Open/Closed Principle
Polymorphism enables the Open/Closed Principle (OCP): code is open for extension (add new Shape subclasses) but closed for modification (the printTotal(Shape[]) method never needs to change). This is the foundation of scalable, maintainable software design.
โญ Must-Know Interview Topics
Two types of polymorphism ยท Dynamic Method Dispatch definition ยท vtable mechanism ยท what participates in DMD (not fields, not static, not private) ยท field hiding trap ยท Open/Closed principle connection ยท parent reference + child object pattern
Q1. What is polymorphism? What are its two types in Java?
Polymorphism means "many forms" โ€” the ability of a single method call to behave differently depending on the object it is called on.

Compile-time polymorphism (static/early binding): achieved via method overloading. The compiler resolves the method call at compile time based on the method signature. The reference type determines which overloaded method runs.

Runtime polymorphism (dynamic/late binding): achieved via method overriding + parent reference pointing to child object. The JVM resolves the method call at runtime based on the actual object type. This is Dynamic Method Dispatch.
Q2. What is Dynamic Method Dispatch?
Dynamic Method Dispatch (DMD) is the JVM mechanism that resolves an overridden instance method call at runtime based on the actual type of the object on the Heap, not the declared type of the reference variable.

The JVM uses a virtual method table (vtable) โ€” a per-class table mapping method names to their most-derived implementation. When a method is called, the JVM follows: reference โ†’ object โ†’ object's class โ†’ vtable โ†’ call the correct override.

This is what makes Animal a = new Dog(); a.sound(); call Dog.sound() instead of Animal.sound().
Q3. What does NOT participate in runtime polymorphism?
Static methods โ€” resolved by reference type at compile time (method hiding)
Instance variables (fields) โ€” also resolved by reference type (field hiding)
Private methods โ€” not inherited, so no vtable entry in child
final methods โ€” cannot be overridden, single vtable entry
Constructors โ€” never inherited or overridden

Only non-static, non-private, non-final instance methods that are overridden participate in DMD.
Q4. Can you achieve runtime polymorphism without inheritance?
Not with classes alone โ€” you need a supertype (class or interface) that both the reference type and the implementing type share. Runtime polymorphism requires:
1. A parent reference (or interface reference)
2. A child/implementing object assigned to it
3. The method overridden in the child

However, with interfaces you don't need class inheritance โ€” a class can implement an interface and be referenced by that interface type, achieving full runtime polymorphism without extending any class. This is actually the preferred form in modern Java and Spring Boot.
Q5. What is the difference between early binding and late binding?
Early binding (compile-time / static binding): the method to call is determined at compile time by the compiler. Applies to: overloaded methods, static methods, private methods, final methods. The binding is "early" because it happens before runtime.

Late binding (runtime / dynamic binding): the method to call is determined at runtime by the JVM based on the actual object type. Applies to: overridden instance methods. The binding is "late" because it is deferred until the program is running.

Dynamic Method Dispatch is Java's implementation of late binding.
Q6. Explain: Animal a = new Dog(); What can you access via `a`?
Via reference a of type Animal:
You CAN access: all members declared in Animal (fields and methods). The compiler uses the reference type (Animal) to check what is accessible.
You CANNOT access: members declared only in Dog (e.g., a.bark() โ†’ compile error โ€” bark() is not in Animal).

At runtime, if you call a.sound() and Dog overrides sound(), the JVM calls Dog's version โ€” DMD.
If you call a.type (a field), you get Animal's field value โ€” no DMD for fields.
To access Dog-specific members, you must downcast: ((Dog)a).bark().
Q7. How does polymorphism relate to the Open/Closed Principle?
The Open/Closed Principle (OCP) states that software entities should be open for extension but closed for modification.

Polymorphism enables this: a method that accepts a Shape parameter works with any current and future Shape subclass without modification. Adding a Pentagon class requires zero changes to the calling code โ€” just write Pentagon and pass it in. The loop for (Shape s : shapes) s.area() works forever without touching it.

Without polymorphism you would need if (shape instanceof Circle) ... else if (shape instanceof Rectangle)... โ€” and every new shape requires editing that code. Polymorphism eliminates those conditionals.

๐ŸŒฑ Polymorphism in Spring Boot

Spring Boot is built on polymorphism. Every bean injection, service interface, and repository relies on runtime polymorphism to swap implementations without changing calling code.

โœ… Polymorphism patterns in Spring Boot
Interface injection โ€” inject by interface type; Spring resolves to the concrete bean at runtime
Multiple implementations โ€” inject a List<PaymentService> to get all implementations
@Primary / @Qualifier โ€” choose which concrete bean to inject when multiple exist
Strategy pattern via Spring โ€” pick payment processor at runtime based on order type
Repository interfaces โ€” JpaRepository is polymorphically resolved to SimpleJpaRepository at runtime
PaymentService interface + implementations โ€” Spring polymorphism
// Interface โ€” the polymorphic supertype
public interface PaymentService {
    void processPayment(double amount);
    String getType();
}

// Concrete implementations
@Service class UpiPaymentService implements PaymentService {
    public void processPayment(double amount) {
        System.out.println("UPI: paying " + amount);
    }
    public String getType() { return "UPI"; }
}

@Service class CardPaymentService implements PaymentService {
    public void processPayment(double amount) {
        System.out.println("Card: paying " + amount);
    }
    public String getType() { return "CARD"; }
}

// OrderService โ€” inject ALL PaymentService implementations
@Service
public class OrderService {
    private final List<PaymentService> paymentServices;

    public OrderService(List<PaymentService> services) {
        this.paymentServices = services;
    }

    public void pay(String type, double amount) {
        paymentServices.stream()
            .filter(s -> s.getType().equalsIgnoreCase(type))
            .findFirst()
            .ifPresent(s -> s.processPayment(amount));
            // DMD: calls the right processPayment() at runtime
    }
}
๐Ÿ“Œ Spring IoC is polymorphism at the framework level
Spring's dependency injection is polymorphism applied at the container level. When you declare private final PaymentService paymentSvc, Spring decides at runtime which concrete bean to inject โ€” UpiPaymentService or CardPaymentService. The calling class never needs to know which implementation it got. This is the Strategy pattern powered by runtime polymorphism.
โญ Must-Know Exam Points
  • Compile-time polymorphism โ†’ method overloading โ†’ resolved by reference type at compile time
  • Runtime polymorphism โ†’ method overriding โ†’ resolved by object type at runtime
  • Dynamic Method Dispatch = JVM mechanism for runtime polymorphism via vtable
  • Fields (instance variables) use early binding โ€” NOT polymorphic
  • Static methods use early binding โ€” NOT polymorphic (method hiding)
  • private, final methods โ†’ NOT polymorphic
  • Pattern: Parent ref = new Child() โ€” method call uses Child's override
  • Cannot call Child-specific methods via Parent reference (compile error)
  • Downcast needed: ((Child)ref).childMethod()

๐Ÿ“ MCQ Traps

Code / QuestionOutput / AnswerTrap / Reason
Animal a=new Dog(); a.sound() where Dog overrides sound()Dog's sound()Runtime polymorphism โ€” object type wins
Animal a=new Dog(); a.type where both have field typeAnimal's type valueFields use reference type โ€” no DMD for fields
Animal a=new Dog(); a.staticMethod()Animal's static methodStatic uses reference type โ€” method hiding, not overriding
Overloading is which type of polymorphism?Compile-timeResolved at compile time by signature โ€” not runtime
Can private method be polymorphic?NoPrivate not inherited โ€” child's same-name method is a NEW method
What resolves which overloaded method to call?Argument types at compile timeThe compiler picks based on declared argument types, not runtime types

๐Ÿ“– One-Liner Definitions

  • Polymorphism โ€” The ability of one interface/method to take many forms; implemented via overloading (compile-time) and overriding (runtime) in Java.
  • Dynamic Method Dispatch โ€” JVM mechanism that resolves overridden method calls at runtime based on the actual object type, using a virtual method table (vtable).
  • Early binding โ€” Method resolution at compile time; applies to overloaded, static, private, and final methods.
  • Late binding โ€” Method resolution at runtime; applies to overridden instance methods; implemented via vtable lookup.
  • vtable (virtual method table) โ€” A per-class table maintained by the JVM mapping method names to their most-derived implementations; the mechanism behind DMD.
  • Field hiding โ€” When a child class declares a field with the same name as the parent; resolved by reference type (not runtime type) โ€” does not participate in polymorphism.
TOPIC 20

final Keyword

๐Ÿ”’ The final Keyword โ€” Three Contexts

The final keyword means "cannot be changed after this point." It applies in three completely different contexts โ€” variable, method, and class โ€” and the meaning is analogous in each: lock it down.

THREE USES OF final โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. final variable โ†’ value cannot be reassigned after init 2. final method โ†’ cannot be overridden by any subclass 3. final class โ†’ cannot be extended (no subclasses allowed) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

1๏ธโƒฃ final Variable

A final variable can be assigned only once. After the first assignment it becomes read-only. This applies to local variables, method parameters, instance fields, and static fields.

FINAL VARIABLE RULES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ A) final local variable โ€” must be assigned before use: final int x = 10; x = 20; // โŒ compile error B) final instance field โ€” must be assigned in: โ†’ Declaration: final int MAX = 100; โ†’ Constructor (any): this.MAX = 100; inside constructor โ†’ Instance init block: { MAX = 100; } NOT in a regular method after construction! C) final static field (constant): static final double PI = 3.14159; โ†’ Assigned at declaration or in static initialiser block โ†’ Convention: ALL_CAPS_WITH_UNDERSCORES D) final reference variable: final List list = new ArrayList<>(); list.add("hello"); // โœ… โ€” list content CAN change list = new ArrayList<>(); // โŒ โ€” reference itself cannot change final = makes the REFERENCE constant, not the OBJECT!
โš ๏ธ final reference โ‰  immutable object
A final reference variable means you cannot reassign the variable to point to a different object. But the object itself can still be mutated through its own methods. To make a truly immutable object, you need the class itself to be designed for immutability (private fields, no setters, final class โ€” like String).

2๏ธโƒฃ final Method

A final method cannot be overridden by any subclass. The implementation is locked. Useful when a method's behaviour must not change in subclasses for correctness or security reasons.

FINAL METHOD โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Account { final void debit(double amount) { // locked โ€” cannot be changed if (amount <= 0) throw new IllegalArgumentException(); balance -= amount; } void getBalance() { ... } // can be overridden } class SavingsAccount extends Account { // @Override void debit(...) { } // โŒ compile error! @Override void getBalance() { ... } // โœ… non-final โ€” OK } Use case: security-critical logic, template method pattern where the skeleton must not change, factory creation steps.

3๏ธโƒฃ final Class

A final class cannot be extended. No class can declare extends FinalClass. This is the strongest form of locking โ€” the class design is frozen completely.

FINAL CLASS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ public final class String { ... } // JDK โ€” cannot be subclassed public final class Integer { ... } // JDK wrapper โ€” same public final class Math { ... } // JDK โ€” same class MyString extends String { } // โŒ compile error! Why make a class final? โ†’ Security: prevent malicious subclasses from overriding methods โ†’ Immutability: ensures no subclass can add mutable state โ†’ Performance: JVM can inline final class method calls (devirtualise) โ†’ Design: the class is intentionally complete โ€” extension is wrong Note: all methods in a final class are implicitly final too. You cannot instantiate a final class differently โ€” just cannot extend it.

๐Ÿ”— final + static = Constant

CONSTANTS โ€” static final โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ public class MathConstants { public static final double PI = 3.14159265358979; public static final int MAX_AGE = 150; public static final String APP_NAME = "MyApp"; } Access: MathConstants.PI // class name dot constant name MathConstants.MAX_AGE Convention: โ†’ ALL_CAPS_WITH_UNDERSCORES for constant names โ†’ public static final for shared constants โ†’ Compile-time constants (primitives + String) are inlined by compiler into every usage site
๐Ÿ’ก Prefer final everywhere you can
Using final on local variables, fields, and parameters wherever possible:
โ€” Makes code easier to reason about (value won't change unexpectedly)
โ€” Enables compiler and JVM optimisations
โ€” Catches accidental reassignment bugs at compile time
โ€” Signals intent to other developers: "this is not meant to change"
Modern Java style: constructor-injected Spring beans are private final fields โ€” immutable references.

๐Ÿ’ป Program 1 โ€” final Variable in All Contexts

FinalVariable.java
import java.util.ArrayList;
import java.util.List;

public class FinalVariable {

    // C) static final constant โ€” class-level, shared, immutable ref
    public static final double TAX_RATE  = 0.18;
    public static final String APP_NAME  = "ShopApp";

    // B) final instance field โ€” must be set in constructor
    private final String orderId;
    private final double basePrice;

    public FinalVariable(String orderId, double basePrice) {
        this.orderId   = orderId;    // assigned once here
        this.basePrice = basePrice;  // assigned once here
    }

    public void processOrder() {
        // A) final local variable
        final double tax   = basePrice * TAX_RATE;
        final double total = basePrice + tax;
        // tax = 0;  โ† compile error: cannot assign to final

        System.out.printf("[%s] App: %s%n", orderId, APP_NAME);
        System.out.printf("Base: %.2f | Tax(%.0f%%): %.2f | Total: %.2f%n",
            basePrice, TAX_RATE * 100, tax, total);
    }

    public static void demonstrateFinalRef() {
        // D) final reference โ€” reference locked, object mutable
        final List<String> items = new ArrayList<>();
        items.add("Apple");   // โœ… mutating the object is fine
        items.add("Mango");
        System.out.println("Items: " + items);
        // items = new ArrayList<>();  โ† compile error: final ref!
    }

    public static void main(String[] args) {
        new FinalVariable("ORD-001", 500).processOrder();
        demonstrateFinalRef();
    }
}
โ–ถ OUTPUT
[ORD-001] App: ShopApp Base: 500.00 | Tax(18%): 90.00 | Total: 590.00 Items: [Apple, Mango]

๐Ÿ’ป Program 2 โ€” final Method & final Class

FinalMethodClass.java
class Payment {
    protected double amount;

    Payment(double amount) { this.amount = amount; }

    // final method โ€” security-critical; no subclass can change this logic
    final void applyGST() {
        amount *= 1.18;
        System.out.printf("GST applied. New amount: %.2f%n", amount);
    }

    // non-final โ€” subclasses can customise
    void processPayment() {
        System.out.println("Generic payment: " + amount);
    }
}

class UpiPayment extends Payment {
    UpiPayment(double amount) { super(amount); }

    // โŒ Cannot override final method:
    // void applyGST() { }   โ†’ compile error

    @Override
    void processPayment() {       // โœ… can override non-final
        System.out.printf("UPI payment via VPA: %.2f%n", amount);
    }
}

// final class โ€” cannot be subclassed
final class SSLCertificate {
    private final String issuer;
    private final String subject;

    SSLCertificate(String issuer, String subject) {
        this.issuer  = issuer;
        this.subject = subject;
    }
    void display() {
        System.out.printf("Cert โ€” Issuer: %s | Subject: %s%n",
            issuer, subject);
    }
}
// class HackedCert extends SSLCertificate {}  โ† compile error!

public class FinalMethodClass {
    public static void main(String[] args) {
        UpiPayment upi = new UpiPayment(1000);
        upi.applyGST();        // calls Payment.applyGST() โ€” locked
        upi.processPayment();  // calls UpiPayment.processPayment() โ€” overridden

        new SSLCertificate("DigiCert", "example.com").display();
    }
}
โ–ถ OUTPUT
GST applied. New amount: 1180.00 UPI payment via VPA: 1180.00 Cert โ€” Issuer: DigiCert | Subject: example.com

๐Ÿ’ป Program 3 โ€” final Parameter & Blank final

FinalAdvanced.java
public class FinalAdvanced {

    // Blank final โ€” declared but not initialised at declaration
    private final int level;   // blank final field

    FinalAdvanced(int level) {
        this.level = level;    // assigned in constructor โ€” OK
    }

    // final parameter โ€” cannot be reassigned inside method body
    static double calculateDiscount(final double price,
                                       final double rate) {
        // price = 0;  โ† compile error: final parameter
        return price * rate;
    }

    // final in enhanced for โ€” each iteration variable is effectively final
    static void printSquares(int[] nums) {
        for (final int n : nums) {
            System.out.printf("%d^2 = %d%n", n, n*n);
            // n++;  โ† compile error: final
        }
    }

    public static void main(String[] args) {
        FinalAdvanced fa = new FinalAdvanced(3);
        System.out.println("Level: " + fa.level);

        double discount = calculateDiscount(2000, 0.10);
        System.out.printf("Discount (10%% of 2000): %.2f%n", discount);

        printSquares(new int[]{2, 3, 4});
    }
}
โ–ถ OUTPUT
Level: 3 Discount (10% of 2000): 200.00 2^2 = 4 3^2 = 9 4^2 = 16
๐Ÿ’ก Effectively final (Java 8+)
A variable that is never reassigned after initialisation is effectively final, even without the final keyword. Lambda expressions and anonymous classes can capture effectively final local variables โ€” they behave just like explicitly final variables from the compiler's perspective. If you try to reassign an effectively final variable that a lambda captures, you get a compile error.
โญ Must-Know Interview Topics
Three uses of final ยท final reference vs immutable object ยท blank final ยท final + static = constant ยท final method cannot be overridden ยท final class cannot be extended ยท String is final class ยท effectively final (Java 8)
Q1. What are the three uses of the final keyword?
1. final variable: can be assigned only once; becomes read-only after first assignment. Applies to local variables, method parameters, instance fields, and static fields. A static final field is a constant.

2. final method: cannot be overridden by any subclass. The implementation is locked. Useful for security-critical logic and template method patterns.

3. final class: cannot be extended โ€” no class can use extends FinalClass. All methods are implicitly final. Used for security, immutability, and design completeness. Examples: String, Integer, Math.
Q2. Does final on a reference variable make the object immutable?
No. A final reference variable means the reference cannot be reassigned to point to a different object. The object itself can still be modified through its own methods.

Example: final List<String> list = new ArrayList<>();
โ€” list.add("hello") โœ… โ€” modifying the object is allowed
โ€” list = new ArrayList<>() โŒ โ€” reassigning the reference is forbidden

To create a truly immutable object: make the class final, make all fields private and final, provide no setters, and return defensive copies of mutable fields. String is the canonical example.
Q3. What is a blank final variable?
A blank final is a final instance field that is declared without an initial value at the point of declaration. It must be assigned exactly once before the constructor finishes โ€” either inside every constructor path or in an instance initialiser block.

private final int level; โ€” blank final
FinalAdvanced(int l) { this.level = l; } โ€” assigned in constructor

Blank finals are useful when the value depends on constructor arguments and cannot be known at class load time. If any constructor path fails to assign it, the compiler reports an error.
Q4. Why is the String class declared final?
String is final for three key reasons:

Security: if String could be subclassed, a malicious subclass could override methods like hashCode() or equals() to break collections, or override internal methods to bypass security checks (e.g., in file paths or network URLs).

String pool (interning): Java maintains a pool of String literals. If String were mutable or subclassable, the shared pool would be unsafe โ€” one reference modifying a pooled String would corrupt all others sharing it.

Thread safety: immutable strings are inherently thread-safe โ€” no synchronisation needed since the state never changes after creation.
Q5. Can a final class have non-final methods?
Yes โ€” a final class can contain methods that are not explicitly marked final. However, since the class itself cannot be extended, those methods effectively become final anyway โ€” there is no subclass that could override them.

Declaring individual methods as final inside a final class is redundant but not a compile error.
Q6. What is "effectively final" in Java 8+?
A local variable or parameter is effectively final if it is never reassigned after its initial assignment, even without the explicit final keyword.

Java 8 allows lambda expressions and anonymous classes to capture local variables from the enclosing scope if they are final or effectively final.

int x = 10; // effectively final โ€” never reassigned
Runnable r = () -> System.out.println(x); // โœ…

If you later add x = 20, x is no longer effectively final, and the lambda capture becomes a compile error. This rule ensures lambda closures are safe โ€” they capture a stable value, not a changing variable.

๐ŸŒฑ final in Spring Boot

The final keyword is used throughout Spring Boot best practices โ€” especially for dependency injection fields and configuration constants.

โœ… final patterns in Spring Boot
private final fields โ€” constructor-injected dependencies; guarantees they are set once and never changed
static final constants โ€” shared configuration keys, header names, path prefixes
final local variables in service methods โ€” makes data flow traceable and safe
@Value injected final fields โ€” not directly possible with constructor injection (use @ConfigurationProperties instead)
ProductController.java + AppConstants.java โ€” final in Spring Boot
// โ”€โ”€ Constants file โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
public final class AppConstants {         // final class โ€” not extendable
    private AppConstants() {}             // private constructor โ€” no instances

    public static final String API_V1       = "/api/v1";
    public static final String AUTH_HEADER  = "Authorization";
    public static final    int MAX_PAGE_SIZE = 100;
    public static final   long JWT_EXPIRY_MS = 86_400_000L; // 24h
}

// โ”€โ”€ Controller with private final dependencies โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@RestController
@RequestMapping(AppConstants.API_V1 + "/products")
public class ProductController {

    private final ProductService  productService;   // immutable ref
    private final CategoryService categoryService;  // immutable ref

    // Constructor injection โ€” Spring sets these once, never again
    public ProductController(ProductService ps, CategoryService cs) {
        this.productService  = ps;
        this.categoryService = cs;
    }

    @GetMapping("/{id}")
    public ResponseEntity<Product> getById(@PathVariable Long id) {
        final Product product = productService.findById(id);
        return ResponseEntity.ok(product);
    }
}
๐Ÿ“Œ Why private final for injected dependencies?
Using private final for Spring-injected fields (via constructor injection) provides:
1. Null safety: if the bean is not provided, the app fails to start โ€” not with a NullPointerException later
2. Immutability: the dependency cannot be swapped out at runtime
3. Testability: constructor injection makes the dependencies explicit โ€” easy to pass mocks in unit tests
4. Lombok: @RequiredArgsConstructor auto-generates the constructor for all private final fields
โญ Must-Know Exam Points
  • final variable โ†’ assigned once only; after that read-only
  • final method โ†’ cannot be overridden; can be inherited and called
  • final class โ†’ cannot be extended; all methods effectively final
  • static final = constant; convention: ALL_CAPS_UNDERSCORES
  • final reference โ‰  immutable object โ€” reference locked, object mutable
  • Blank final = declared without value; must be set in constructor
  • final instance field: must be assigned by the time any constructor completes
  • String, Integer, Math are all final classes
  • Effectively final (Java 8+) โ€” never reassigned; can be captured by lambdas

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Can you call a final method from a subclass?Yes โ€” it can be inherited and calledfinal only blocks OVERRIDING, not calling or inheriting
Can a final class have abstract methods?No โ€” compile errorabstract requires subclassing to implement; final blocks subclassing โ€” contradiction
final List<String> list; list.add("x") โ€” valid?Yesfinal locks the reference, not the object contents
Where can a blank final field be assigned?Constructor or instance init blockCannot be assigned in a regular method after construction
Can you override a final method in the same class?N/A โ€” same class doesn't override, it definesfinal only restricts SUBCLASSES from overriding
Is String a final class?Yesjava.lang.String is final โ€” cannot be subclassed
What happens if a lambda captures a reassigned local variable?Compile errorLambda requires variable to be final or effectively final

๐Ÿ“– One-Liner Definitions

  • final variable โ€” A variable that can be assigned exactly once; read-only after its first assignment.
  • final method โ€” A method that cannot be overridden by any subclass; can still be inherited and called.
  • final class โ€” A class that cannot be subclassed; all its methods are effectively final.
  • blank final โ€” A final instance field declared without an initial value; must be assigned in every constructor path.
  • static final constant โ€” A class-level constant shared across all instances; named in ALL_CAPS_UNDERSCORES by convention.
  • effectively final โ€” A local variable that is never reassigned after initialisation; can be captured by lambdas as if explicitly final (Java 8+).
TOPIC 21

Object Class โ€” toString, equals, hashCode

๐Ÿ›๏ธ java.lang.Object โ€” The Root of All Classes

Every class in Java implicitly extends java.lang.Object. This means every object you ever create โ€” whether from your own class, a JDK class, or a library โ€” inherits the methods defined in Object. Three of these methods are critical to override correctly: toString(), equals(), and hashCode().

OBJECT CLASS KEY METHODS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Method Default Behaviour (if NOT overridden) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ toString() ClassName@hexHashCode e.g. "Student@1b6d3586" โ† useless for debugging equals(Object o) Reference equality: this == o Two different objects are NEVER equal even if they have the same field values hashCode() Memory-address-based integer (implementation-defined) Not consistent with equals() for value comparison getClass() Returns the runtime Class object (final โ€” not overridable) clone() Shallow copy โ€” requires Cloneable interface finalize() Called before GC โ€” deprecated in Java 9+ wait()/notify() Thread synchronisation primitives โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

๐Ÿ“ toString()

toString() is called automatically whenever an object is used in a String context โ€” printing, concatenation, logging. The default implementation returns something like Student@1b6d3586 which is useless. Always override it to return a meaningful, human-readable representation.

toString() โ€” WHEN IT IS CALLED AUTOMATICALLY โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Student s = new Student("Rahul", 20); System.out.println(s); // calls s.toString() System.out.println("Hi " + s); // String concat calls toString() log.info("{}", s); // loggers call toString() String str = String.valueOf(s); // calls toString() Default output: Student@1b6d3586 After override: Student{name='Rahul', age=20} โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

๐ŸŸฐ equals()

The default equals() uses reference equality (==): two objects are equal only if they are the exact same object in memory. For value objects (Student, Book, Point), you almost always want content equality: two objects are equal if their meaningful fields have the same values.

equals() โ€” DEFAULT VS OVERRIDDEN โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Student s1 = new Student("Rahul", 20); Student s2 = new Student("Rahul", 20); // same values, different object // Without override: s1.equals(s2) โ†’ false (different references on Heap) s1 == s2 โ†’ false (obviously) // With override (compare by name + age): s1.equals(s2) โ†’ true (same field values) The CONTRACT that equals() must follow (from Object javadoc): 1. Reflexive: x.equals(x) must be true 2. Symmetric: x.equals(y) iff y.equals(x) 3. Transitive: x.equals(y) && y.equals(z) โ†’ x.equals(z) 4. Consistent: multiple calls return same result (if no change) 5. Null-safe: x.equals(null) must return false (never throw NPE)

#๏ธโƒฃ hashCode()

hashCode() returns an integer used by hash-based collections (HashMap, HashSet, Hashtable) to locate objects efficiently in a bucket structure. The golden rule: if two objects are equal (via equals()), they MUST return the same hashCode. The reverse is not required โ€” two objects with the same hashCode can be unequal (a collision).

THE equals-hashCode CONTRACT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Rule: equals() true โ†’ hashCode() must be same hashCode() same โ†’ equals() may be true or false (collision) Why it matters โ€” HashMap lookup process: 1. Compute key.hashCode() โ†’ find the bucket 2. In that bucket, call key.equals(existingKey) โ†’ confirm identity If you override equals() but NOT hashCode(): s1.equals(s2) โ†’ true (your override) s1.hashCode() != s2.hashCode() (different memory addresses) HashMap map = new HashMap(); map.put(s1, "data"); map.get(s2) โ†’ null โ† WRONG! s2 goes to wrong bucket! HashSet.contains(s2) โ†’ false โ† WRONG! Always override BOTH equals() and hashCode() together.
โš ๏ธ The cardinal rule: override equals AND hashCode together
If you override equals() without hashCode() (or vice versa), your class will behave incorrectly in all hash-based collections: HashMap, HashSet, LinkedHashMap, Hashtable. This is one of the most common subtle bugs in Java. Your IDE can generate both together โ€” always do so.
๐Ÿ’ก Use Objects.equals() and Objects.hash() for null-safe implementations
java.util.Objects.equals(a, b) โ€” null-safe: returns false if either is null, never throws NPE
java.util.Objects.hash(field1, field2, ...) โ€” computes a combined hash of multiple fields
These are the preferred building blocks for manual equals/hashCode implementations.

๐Ÿ’ป Program 1 โ€” Default vs Overridden toString, equals, hashCode

ObjectMethods.java
import java.util.Objects;

class Student {
    private String name;
    private int    rollNo;

    public Student(String name, int rollNo) {
        this.name   = name;
        this.rollNo = rollNo;
    }

    // 1. toString โ€” human-readable representation
    @Override
    public String toString() {
        return "Student{name='" + name + "', roll=" + rollNo + "}";
    }

    // 2. equals โ€” content equality by name + rollNo
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;                   // same ref fast path
        if (!(o instanceof Student)) return false;    // null + type check
        Student other = (Student) o;
        return rollNo == other.rollNo
            && Objects.equals(name, other.name);     // null-safe compare
    }

    // 3. hashCode โ€” consistent with equals (same fields)
    @Override
    public int hashCode() {
        return Objects.hash(name, rollNo);              // combines both fields
    }
}

public class ObjectMethods {
    public static void main(String[] args) {
        Student s1 = new Student("Rahul", 101);
        Student s2 = new Student("Rahul", 101);
        Student s3 = new Student("Priya", 102);

        // toString
        System.out.println(s1);                          // auto toString()
        System.out.println("Student: " + s2);            // concat toString()

        // equals
        System.out.println("s1==s2 (ref): "  + (s1 == s2));      // false
        System.out.println("s1.eq(s2):    "  + s1.equals(s2));   // true
        System.out.println("s1.eq(s3):    "  + s1.equals(s3));   // false
        System.out.println("s1.eq(null):  "  + s1.equals(null)); // false (no NPE)

        // hashCode
        System.out.printf("hash(s1)=%d  hash(s2)=%d  match=%b%n",
            s1.hashCode(), s2.hashCode(),
            s1.hashCode() == s2.hashCode());
    }
}
โ–ถ OUTPUT
Student{name='Rahul', roll=101} Student: Student{name='Rahul', roll=101} s1==s2 (ref): false s1.eq(s2): true s1.eq(s3): false s1.eq(null): false hash(s1)=hash(s2) match=true

๐Ÿ’ป Program 2 โ€” Breaking HashSet Without hashCode Override

HashCodeBug.java โ€” what happens without hashCode
import java.util.HashSet;
import java.util.Objects;

// Broken: equals overridden but hashCode is NOT
class BrokenBook {
    String isbn;
    BrokenBook(String isbn) { this.isbn = isbn; }
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof BrokenBook)) return false;
        return Objects.equals(isbn, ((BrokenBook)o).isbn);
    }
    // hashCode NOT overridden โ†’ uses Object default (memory address)
}

// Fixed: both equals AND hashCode overridden
class FixedBook {
    String isbn;
    FixedBook(String isbn) { this.isbn = isbn; }
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof FixedBook)) return false;
        return Objects.equals(isbn, ((FixedBook)o).isbn);
    }
    @Override
    public int hashCode() { return Objects.hash(isbn); }
}

public class HashCodeBug {
    public static void main(String[] args) {
        // BROKEN โ€” equals says same, but HashSet stores both!
        HashSet<BrokenBook> broken = new HashSet<>();
        broken.add(new BrokenBook("978-1"));
        broken.add(new BrokenBook("978-1"));  // same ISBN โ€” should be duplicate!
        System.out.println("BrokenBook set size: " + broken.size()); // 2 โ† BUG!

        // FIXED โ€” HashSet correctly deduplicates
        HashSet<FixedBook> fixed = new HashSet<>();
        fixed.add(new FixedBook("978-1"));
        fixed.add(new FixedBook("978-1"));
        System.out.println("FixedBook  set size: " + fixed.size());  // 1 โ† correct
    }
}
โ–ถ OUTPUT
BrokenBook set size: 2 FixedBook set size: 1

๐Ÿ’ป Program 3 โ€” All Object Methods + instanceof pattern (Java 16+)

ModernEquals.java โ€” Java 16 pattern matching in equals
import java.util.Objects;

class Point {
    private final int x, y;

    public Point(int x, int y) { this.x = x; this.y = y; }

    @Override
    public String toString() {
        return "Point(" + x + ", " + y + ")";
    }

    // Modern Java 16+ pattern matching โ€” cleaner equals
    @Override
    public boolean equals(Object o) {
        return o instanceof Point p   // pattern var p โ€” cast + check in one
            && x == p.x
            && y == p.y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}

public class ModernEquals {
    public static void main(String[] args) {
        Point p1 = new Point(3, 4);
        Point p2 = new Point(3, 4);
        Point p3 = new Point(1, 2);

        System.out.println(p1);                      // Point(3, 4)
        System.out.println("p1.equals(p2): " + p1.equals(p2));  // true
        System.out.println("p1.equals(p3): " + p1.equals(p3));  // false
        System.out.println("getClass: " + p1.getClass().getSimpleName());

        // Symmetry check
        System.out.println("Symmetric: " + (p1.equals(p2) == p2.equals(p1)));
        // hashCode consistency
        System.out.println("Hash match: " + (p1.hashCode() == p2.hashCode()));
    }
}
โ–ถ OUTPUT
Point(3, 4) p1.equals(p2): true p1.equals(p3): false getClass: Point Symmetric: true Hash match: true
๐Ÿ“Œ IDE Generation โ€” never write equals/hashCode by hand in production
IntelliJ IDEA: Alt+Insert โ†’ Generate โ†’ equals() and hashCode()
Eclipse: Right-click โ†’ Source โ†’ Generate hashCode() and equals()
Lombok: add @EqualsAndHashCode annotation to generate at compile time
Java 16+ Records: record Point(int x, int y) {} โ€” equals/hashCode/toString auto-generated!
โญ Must-Know Interview Topics
equals-hashCode contract ยท what breaks without hashCode ยท five rules of equals contract ยท HashMap lookup algorithm ยท toString auto-call ยท Objects.equals / Objects.hash ยท why override both together ยท Java record auto-generation
Q1. What is the contract between equals() and hashCode()?
The Java specification defines a strict contract:

1. If a.equals(b) is true, then a.hashCode() == b.hashCode() must be true.
2. If a.hashCode() == b.hashCode(), then a.equals(b) may or may not be true (hash collision is allowed).

The reverse is not required โ€” two unequal objects can share a hash code (this is a collision and degrades HashMap performance to O(n) for that bucket). But two equal objects that return different hash codes will break HashMap, HashSet, Hashtable, and any hash-based collection.
Q2. What happens if you override equals() but not hashCode()?
Hash-based collections completely break for that class:

HashMap: map.get(key2) returns null even though an equal key was put in โ€” because key2's hash goes to a different bucket than key1's hash.
HashSet: stores logically duplicate objects (two objects that are equals() end up in different buckets, so contains() returns false and both are added).

This is a silent bug โ€” no exception is thrown. The code compiles and runs; it just produces wrong results. IDEs and tools like SonarQube flag this as a critical warning.
Q3. What are the five rules the equals() contract must satisfy?
From the Object Javadoc โ€” any correctly implemented equals() must be:

1. Reflexive: x.equals(x) must always be true
2. Symmetric: x.equals(y) iff y.equals(x)
3. Transitive: if x.equals(y) and y.equals(z), then x.equals(z)
4. Consistent: repeated calls return same result as long as neither object is modified
5. Null-safe: x.equals(null) must return false, never throw NullPointerException
Q4. When is toString() called automatically?
Java calls toString() automatically in any context that requires a String representation:
โ€” System.out.println(obj) โ€” print statement
โ€” "prefix " + obj โ€” String concatenation
โ€” String.valueOf(obj) โ€” explicit conversion
โ€” Logger frameworks like SLF4J: log.info("{}", obj)
โ€” StringBuilder.append(obj)

If not overridden, the default returns ClassName@hexHashCode (e.g., Student@1b6d3586) โ€” useless for debugging and logging.
Q5. How does HashMap use hashCode() and equals() together?
HashMap uses a two-step lookup:

Step 1 โ€” Find the bucket: computes key.hashCode(), applies a secondary hash function, and uses the result to determine the array index (bucket).
Step 2 โ€” Find the entry: iterates through all entries in that bucket and calls key.equals(existingKey) to find the exact match.

hashCode is for efficiency (quickly narrowing candidates to one bucket); equals is for correctness (finding the exact match). If hashCode is wrong, you reach the wrong bucket and equals never even gets a chance to run.
Q6. What is the difference between == and equals() for String?
== compares references โ€” are the two variables pointing to the exact same object in memory?
equals() for String compares content โ€” do the two strings have the same sequence of characters?

String a = "hello";
String b = "hello";
a == b โ†’ true only because of String pool interning (both point to same literal)

String c = new String("hello");
a == c โ†’ false (c is a new heap object, not from the pool)
a.equals(c) โ†’ true (same content)

Always use equals() for String comparison in logic; == only for reference identity checks.

๐ŸŒฑ equals, hashCode, toString in Spring Boot

These three methods have specific implications in Spring Boot โ€” especially for JPA entities, caching, and logging.

โœ… Spring Boot patterns for Object methods
JPA Entity toString() โ€” never include lazy-loaded collections (triggers N+1 or LazyInitializationException)
JPA Entity equals() โ€” use database ID only; if ID is null (transient entity) use reference equality
JPA Entity hashCode() โ€” return a constant or use only the type; avoid mutable fields
DTOs and value objects โ€” freely override all three with all meaningful fields
Lombok on entities โ€” avoid @Data (generates problematic equals/hashCode); use @Getter @Setter @ToString(exclude={"orders"})
Product.java โ€” JPA entity best-practice equals/hashCode/toString
@Entity
public class Product {
    @Id @GeneratedValue
    private Long   id;
    private String name;
    private double price;

    @OneToMany(mappedBy = "product")
    private List<OrderItem> items;   // lazy collection

    // โœ… toString โ€” EXCLUDE lazy collections!
    @Override
    public String toString() {
        return "Product{id=" + id + ", name='" + name
             + "', price=" + price + "}";
        // NOT including items โ€” would trigger lazy load!
    }

    // โœ… equals โ€” use DB id only (follow Hibernate best practice)
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Product)) return false;
        Product other = (Product) o;
        return id != null && id.equals(other.id);
        // if id is null (unsaved entity) โ†’ reference equality only
    }

    // โœ… hashCode โ€” constant, stable across entity lifecycle
    @Override
    public int hashCode() {
        return getClass().hashCode();
        // constant per class โ€” entities in Sets remain stable
        // even when their id changes from null โ†’ generated value
    }
}
โš ๏ธ Never use @Data on JPA entities
Lombok's @Data generates equals() and hashCode() using all fields including collection fields. This causes:
1. StackOverflowError if two entities have bidirectional relationships (A has List<B>, B has A โ€” infinite recursion)
2. LazyInitializationException in toString if lazy collections are included
3. Incorrect Set behaviour if entity's id changes (hash changes after persist)
Use @Getter @Setter @ToString(exclude = "items") instead, and write equals/hashCode manually or with a business-key approach.
โญ Must-Know Exam Points
  • Every class inherits from java.lang.Object
  • Default equals() โ†’ reference equality (==)
  • Default hashCode() โ†’ memory-address-based integer
  • Default toString() โ†’ ClassName@hexHashCode
  • equals-hashCode contract: equals true โ†’ same hashCode (mandatory)
  • Override both equals and hashCode together โ€” always
  • equals contract: reflexive, symmetric, transitive, consistent, null-safe
  • Objects.equals(a,b) โ€” null-safe; Objects.hash(f1,f2) โ€” combined hash
  • == for String โ†’ reference; .equals() for String โ†’ content
  • Java records auto-generate all three methods

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Two objects with equals()=true โ€” must hashCode() be same?Yes โ€” mandatoryCore contract; violating it breaks all hash collections
Two objects with same hashCode โ€” must equals() be true?No โ€” collision allowedHash collision is valid; equals() is the definitive check
Default equals() on two new objects with same fields?falseDefault uses reference (==); objects are different heap locations
What does default toString() return?ClassName@hexHashCodeNot class name alone โ€” includes @ and hash in hex
Override equals but not hashCode โ€” HashSet.add() twice same value?Size = 2 (bug!)Different hashCodes โ†’ different buckets โ†’ duplicate stored
"abc" == new String("abc")?falsenew String() creates a new heap object outside the string pool
Which Object method is final (cannot be overridden)?getClass()wait(), notify(), notifyAll() are also final; toString/equals/hashCode are not

๐Ÿ“– One-Liner Definitions

  • toString() โ€” Returns a human-readable String representation of an object; called automatically in print and concatenation contexts.
  • equals() โ€” Defines logical equality between objects; must be reflexive, symmetric, transitive, consistent, and null-safe.
  • hashCode() โ€” Returns an integer used by hash collections for bucket placement; must be consistent with equals(): if a.equals(b) then a.hashCode() == b.hashCode().
  • equals-hashCode contract โ€” The rule that equal objects must produce the same hash code; violating it silently breaks HashMap and HashSet.
  • Objects.hash() โ€” Utility method in java.util.Objects that computes a combined hash of multiple fields; preferred over manual prime multiplication.
  • Reference equality (==) โ€” Checks if two variables point to the same object in memory; distinct from content equality tested by equals().
TOPIC 22

Upcasting & Downcasting

๐Ÿ”ผ๐Ÿ”ฝ What is Casting?

Casting is treating an object reference as a different type. In an inheritance hierarchy you can move the reference up toward the parent (upcasting) or down toward the child (downcasting). The object itself never changes โ€” only the reference type changes, which determines what members are accessible via that reference.

CASTING IN THE INHERITANCE HIERARCHY โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Object โ””โ”€โ”€ Animal โ† top of our hierarchy โ”œโ”€โ”€ Dog โ† concrete child โ””โ”€โ”€ Cat โ† concrete child UPCASTING โ†‘ child โ†’ parent (implicit, always safe) DOWNCASTING โ†“ parent โ†’ child (explicit, may throw at runtime) Dog d = new Dog(); Animal a = d; // UPCAST โ€” implicit, no cast syntax needed Dog d2 = (Dog) a; // DOWNCAST โ€” explicit cast operator required Cat c = (Cat) a; // DOWNCAST โ€” compiles, but throws ClassCastException // at runtime because the actual object is Dog!

๐Ÿ”ผ Upcasting

Upcasting assigns a child object to a parent reference. It is implicit (no cast syntax needed) and always safe โ€” a Dog IS-A Animal, so treating a Dog as an Animal is always valid. The trade-off: you can only access members declared in the parent type through that reference.

UPCASTING โ€” IMPLICIT AND SAFE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Dog dog = new Dog("Bruno"); Animal animal = dog; // implicit upcast โ€” no () needed Object obj = dog; // upcast to Object โ€” always valid Through `animal` reference (type = Animal): animal.eat(); // โœ… defined in Animal animal.name; // โœ… defined in Animal animal.bark(); // โŒ compile error โ€” not in Animal // even though the object IS a Dog! Key insight: compiler checks the REFERENCE type for access, JVM uses the OBJECT type for which method body to run (DMD).

๐Ÿ”ฝ Downcasting

Downcasting converts a parent reference back to a child type. It requires an explicit cast operator (ChildType). The compiler allows it (it trusts you), but the JVM checks at runtime whether the actual object really is of that type. If not, it throws ClassCastException.

DOWNCASTING โ€” EXPLICIT, RUNTIME-CHECKED โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Animal animal = new Dog("Bruno"); // upcasted // Explicit downcast โ€” compiler OK, JVM checks at runtime Dog dog = (Dog) animal; // โœ… actual object IS a Dog โ†’ safe dog.bark(); // โœ… now we can call Dog-specific methods Animal animal2 = new Cat("Whiskers"); Dog dog2 = (Dog) animal2; // โŒ ClassCastException at runtime! // actual object is Cat, not Dog Prevention โ€” always use instanceof before downcasting: if (animal instanceof Dog d) { // Java 16 pattern matching d.bark(); // safe โ€” d is already cast }

๐Ÿ” instanceof Operator

instanceof tests whether an object is of a given type (or a subtype). Use it before every downcast to prevent ClassCastException. Java 16 introduced pattern matching for instanceof that combines the check and cast into one expression.

instanceof โ€” CLASSIC vs PATTERN MATCHING (Java 16+) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ // Classic โ€” check then cast (two steps) if (animal instanceof Dog) { Dog d = (Dog) animal; // manual cast d.bark(); } // Java 16 Pattern Matching โ€” one step if (animal instanceof Dog d) { // check + bind in one expression d.bark(); // d is already of type Dog } // instanceof checks the FULL hierarchy: Dog dog = new Dog(); dog instanceof Dog โ†’ true dog instanceof Animal โ†’ true (parent) dog instanceof Object โ†’ true (root) dog instanceof Cat โ†’ false (sibling) null instanceof Anything โ†’ false (never throws) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

๐Ÿ“ Memory View โ€” What Actually Happens

WHAT CHANGES WITH CASTING โ€” ONLY THE REFERENCE TYPE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Dog dog = new Dog("Bruno"); // Heap: [ Dog object | name, breed... ] // โ†‘ Animal a = dog; // SAME object on Heap โ€” nothing copied // reference type changed to Animal // โ†‘ (still points to Dog object) Stack: โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ dog โ”‚ a โ”‚ (Dog)a โ”‚ โ”‚ โ”€โ”€โ–บDog โ”‚ โ”€โ”€โ–บDog โ”‚ โ”€โ”€โ–บDog โ”‚ โ† all point to SAME heap object โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ Casting NEVER creates a new object. It only changes what the compiler lets you ACCESS through that ref.
โš ๏ธ ClassCastException โ€” the downcast failure
ClassCastException is a runtime exception thrown when you try to downcast an object to a type it is not actually an instance of. The compiler cannot catch this (it only checks whether a cast is possible in the hierarchy, not whether it is safe for the actual object). Always guard explicit downcasts with instanceof.
๐Ÿ’ก Prefer polymorphism over downcasting
If you find yourself downcasting frequently, it is a design smell. The need to downcast usually means the parent type should have an additional method, or the code should use interfaces or generics instead. Downcasting bypasses the type system โ€” use it sparingly, always with instanceof.

๐Ÿ’ป Program 1 โ€” Upcast, Downcast, ClassCastException

CastingDemo.java
class Animal {
    String name;
    Animal(String name) { this.name = name; }
    void eat() { System.out.println(name + " eats"); }
}

class Dog extends Animal {
    String breed;
    Dog(String name, String breed) {
        super(name); this.breed = breed;
    }
    void bark() { System.out.println(name + " (" + breed + "): Woof!"); }
}

class Cat extends Animal {
    Cat(String name) { super(name); }
    void meow() { System.out.println(name + ": Meow!"); }
}

public class CastingDemo {
    public static void main(String[] args) {

        // โ”€โ”€ UPCASTING โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        Dog dog = new Dog("Bruno", "Labrador");
        Animal a = dog;           // implicit upcast โ€” no cast syntax
        a.eat();                  // โœ… in Animal
        // a.bark();             // โŒ compile error โ€” not in Animal

        // โ”€โ”€ SAFE DOWNCAST โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        Dog d2 = (Dog) a;        // explicit downcast โ€” actual obj IS Dog
        d2.bark();               // โœ… now accessible

        // โ”€โ”€ instanceof GUARD before downcast โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        Animal[] animals = {
            new Dog("Rex",     "German Shepherd"),
            new Cat("Whiskers"),
            new Dog("Max",     "Poodle"),
            new Cat("Luna")
        };

        for (Animal animal : animals) {
            animal.eat();                  // polymorphic โ€” all animals eat
            if (animal instanceof Dog d) {  // Java 16 pattern matching
                d.bark();                  // d already cast โ€” no manual cast
            } else if (animal instanceof Cat c) {
                c.meow();
            }
        }

        // โ”€โ”€ ClassCastException demo โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        try {
            Animal cat = new Cat("Luna");
            Dog wrongCast = (Dog) cat;    // Cat โ†’ Dog: RUNTIME error
        } catch (ClassCastException e) {
            System.out.println("Caught: " + e.getClass().getSimpleName());
        }
    }
}
โ–ถ OUTPUT
Bruno eats Bruno (Labrador): Woof! Rex eats Rex (German Shepherd): Woof! Whiskers eats Whiskers: Meow! Max eats Max (Poodle): Woof! Luna eats Luna: Meow! Caught: ClassCastException

๐Ÿ’ป Program 2 โ€” instanceof Chain & getClass()

InstanceofDemo.java
public class InstanceofDemo {
    public static void main(String[] args) {
        Dog dog = new Dog("Bruno", "Labrador");

        // instanceof checks the full inheritance chain
        System.out.println("instanceof Dog:    " + (dog instanceof Dog));
        System.out.println("instanceof Animal: " + (dog instanceof Animal));
        System.out.println("instanceof Object: " + (dog instanceof Object));
        System.out.println("instanceof Cat:    " + (dog instanceof Cat));

        // null instanceof anything โ†’ false (never NPE)
        Animal nullAnimal = null;
        System.out.println("null instanceof:   " + (nullAnimal instanceof Animal));

        // getClass() vs instanceof โ€” exact type vs hierarchy
        Animal a = new Dog("Rex", "Husky");
        System.out.println("getClass: " + a.getClass().getSimpleName()); // Dog
        System.out.println("a instanceof Animal: " + (a instanceof Animal)); // true
        System.out.println("getClass==Animal: " +
            (a.getClass() == Animal.class));  // false โ€” exact type is Dog
    }
}
โ–ถ OUTPUT
instanceof Dog: true instanceof Animal: true instanceof Object: true instanceof Cat: false null instanceof: false getClass: Dog a instanceof Animal: true getClass==Animal: false

๐Ÿ’ป Program 3 โ€” Practical Downcast: Shape Renderer

ShapeRenderer.java โ€” safe downcast pattern in practice
abstract class Shape {
    abstract String type();
}
class Circle    extends Shape {
    double radius;
    Circle(double r) { radius = r; }
    String type() { return "Circle"; }
}
class Rect extends Shape {
    double w, h;
    Rect(double w, double h) { this.w=w; this.h=h; }
    String type() { return "Rect"; }
}

public class ShapeRenderer {

    // Process a mixed array of shapes โ€” downcast only when needed
    static void render(Shape[] shapes) {
        for (Shape s : shapes) {
            // Common behaviour via polymorphism โ€” no cast needed
            System.out.print("Rendering " + s.type() + ": ");

            // Type-specific behaviour โ€” downcast with pattern matching
            if      (s instanceof Circle c)
                System.out.printf("radius=%.1f, area=%.2f%n",
                    c.radius, Math.PI * c.radius * c.radius);
            else if (s instanceof Rect r)
                System.out.printf("w=%.1f h=%.1f, area=%.2f%n",
                    r.w, r.h, r.w * r.h);
        }
    }

    public static void main(String[] args) {
        render(new Shape[] {
            new Circle(5), new Rect(4,6), new Circle(3)
        });
    }
}
โ–ถ OUTPUT
Rendering Circle: radius=5.0, area=78.54 Rendering Rect: w=4.0 h=6.0, area=24.00 Rendering Circle: radius=3.0, area=28.27
โš ๏ธ Compiler vs JVM โ€” what each checks
Compiler check: Is this cast possible in the hierarchy? Dog โ†” Animal โ†’ yes. Dog โ†” String โ†’ no (unrelated) โ†’ compile error.
JVM check: Is the actual heap object really of that type? Cat object cast to Dog โ†’ ClassCastException at runtime.
You can fool the compiler (it can't see what's in the variable at runtime), but you can't fool the JVM.
โญ Must-Know Interview Topics
Upcasting implicit/safe ยท downcasting explicit/runtime-checked ยท ClassCastException cause and prevention ยท instanceof vs getClass() ยท pattern matching instanceof (Java 16) ยท casting never creates new object ยท compiler vs JVM checks
Q1. What is upcasting? Is it safe?
Upcasting is assigning a child object to a parent (or ancestor) reference. It is implicit โ€” no cast syntax is needed โ€” and always safe at both compile time and runtime. The IS-A relationship guarantees it: a Dog IS-A Animal, so any Dog can always be used wherever an Animal is expected.

Trade-off: through the parent reference you can only access members declared in the parent type. Child-specific members are not accessible (compile error). However, if the child overrides a parent method, that overridden version still runs at runtime via Dynamic Method Dispatch.
Q2. What is downcasting? Why can it fail?
Downcasting is converting a parent reference back to a child type using an explicit cast operator: (Dog) animal.

It can fail because the compiler only checks whether the cast is possible in the class hierarchy โ€” it cannot know what actual object is in the variable at runtime. If the actual heap object is a Cat and you cast to Dog, the JVM throws ClassCastException at runtime.

Prevention: always check with instanceof before downcasting. Java 16 pattern matching (instanceof Dog d) makes this a single safe step.
Q3. What is ClassCastException? When is it thrown?
ClassCastException is a RuntimeException thrown by the JVM when an explicit downcast fails at runtime โ€” i.e., the actual object is not an instance of the target type.

Example: Animal a = new Cat(); Dog d = (Dog) a; โ€” the compiler allows this (Dog and Cat are both in the Animal hierarchy, so the cast is structurally possible). At runtime the JVM discovers the heap object is a Cat, not a Dog, and throws ClassCastException.

It is never thrown by upcasting or by instanceof checks. The only way it can occur is from an explicit cast operator (Type).
Q4. What is the difference between instanceof and getClass()?
instanceof: returns true if the object is an instance of the specified type or any subtype. It checks the full inheritance chain. dog instanceof Animal โ†’ true.

getClass(): returns the exact runtime class of the object. dog.getClass() == Animal.class โ†’ false (it returns Dog.class). For exact-type equality you use getClass() == SomeClass.class.

Use instanceof in equals() when subclassing should be considered equal. Use getClass() when only objects of the exact same type should be equal (strict type check). The choice affects how subclass instances behave in equals() comparisons โ€” a frequently debated design decision.
Q5. Does casting create a new object?
No โ€” casting never creates a new object. The same object on the Heap is accessed through a reference of a different declared type. Only the reference variable's type changes; the actual object and its data remain exactly the same.

This is why upcasting and downcasting are very cheap operations โ€” there is no data copying, no allocation, no constructor call. The JVM simply changes how the compiler checks which members are accessible through that reference variable.
Q6. Can you cast between completely unrelated classes?
No โ€” compile error. If two types have no inheritance relationship (neither is an ancestor of the other), the compiler rejects the cast immediately: "inconvertible types". For example, (String) new Dog() is a compile error because String and Dog share no ancestry beyond Object, and the compiler knows a Dog can never be a String.

The compiler only allows casts between types that could theoretically be related at runtime (parent-child or same hierarchy). The JVM then enforces the actual runtime check with ClassCastException if needed.

๐ŸŒฑ Casting in Spring Boot

Casting appears in Spring Boot when working with the ApplicationContext, event listeners, bean retrieval, and handler method arguments.

โœ… Common casting scenarios in Spring Boot
ApplicationContext.getBean() โ€” returns Object; downcast to the specific type
ApplicationEvent subclasses โ€” event listener receives parent type, downcasts to access payload
HttpServletRequest / ServletRequest โ€” upcasted in filter chain, downcasted when servlet-specific features needed
Spring Security Authentication โ€” cast to specific implementation (e.g., UsernamePasswordAuthenticationToken) to access custom principal
ResponseEntity body โ€” upcasted to Object for generic method signatures
EventListener + SecurityContext โ€” casting in Spring Boot
// โ”€โ”€ ApplicationEvent downcast in listener โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
public class OrderEvent extends ApplicationEvent {
    private final String orderId;
    public OrderEvent(Object source, String orderId) {
        super(source); this.orderId = orderId;
    }
    public String getOrderId() { return orderId; }
}

@Component
public class OrderEventListener {

    // ApplicationEvent is the parent โ€” downcast to OrderEvent for payload
    @EventListener
    public void handleEvent(ApplicationEvent event) {
        if (event instanceof OrderEvent oe) {    // safe downcast
            System.out.println("Order placed: " + oe.getOrderId());
        }
    }
}

// โ”€โ”€ Security context โ€” cast principal to custom UserDetails โ”€
@RestController
public class ProfileController {

    @GetMapping("/profile")
    public String getProfile(@AuthenticationPrincipal Object principal) {
        if (principal instanceof CustomUserDetails u) {  // pattern match
            return "Hello, " + u.getDisplayName();
        }
        return "Hello, anonymous";
    }
}
๐Ÿ“Œ Prefer typed @EventListener over generic ApplicationEvent
Rather than receiving ApplicationEvent and downcasting, you can directly type the listener to your specific event: @EventListener public void handle(OrderEvent event). Spring will only invoke this listener when an OrderEvent is published โ€” no downcast needed. This is the preferred, cleaner approach.
โญ Must-Know Exam Points
  • Upcasting โ†’ implicit, always safe, child โ†’ parent
  • Downcasting โ†’ explicit (Type), runtime-checked, parent โ†’ child
  • ClassCastException โ†’ runtime exception from failed downcast
  • Compiler checks: is the cast possible? JVM checks: is the actual object that type?
  • Casting never creates a new object โ€” only reference type changes
  • instanceof โ†’ checks type AND full hierarchy; null instanceof X โ†’ always false
  • getClass() โ†’ exact runtime type only (not hierarchy)
  • Through upcast reference โ†’ can only access parent members (compile check)
  • Through upcast reference โ†’ overridden methods still call child version (DMD)
  • Java 16 pattern matching: instanceof Dog d โ†’ check + cast in one step

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Animal a = new Dog(); โ€” is this upcasting?Yes โ€” implicit upcastDog IS-A Animal; no cast syntax needed; always safe
Dog d = (Dog) new Cat(); โ€” compile error or runtime error?Runtime โ€” ClassCastExceptionCompiler allows (same hierarchy); JVM catches actual type mismatch
Does casting create a new object?NoOnly the reference type changes; same heap object
null instanceof Animal?false โ€” never NPEinstanceof always returns false for null; safe to call without null check
Animal a = new Dog(); a.bark() โ€” compile error?Yes โ€” compile errorbark() not declared in Animal; compiler uses reference type for access check
instanceof vs getClass() for hierarchy?instanceof checks hierarchy; getClass() exact type onlydog instanceof Animal โ†’ true; dog.getClass()==Animal.class โ†’ false
Dog d = (Dog) someAnimalRef; โ€” what does compiler check?Only that Dog and Animal are in the same hierarchyCompiler cannot know the actual object type at compile time

๐Ÿ“– One-Liner Definitions

  • Upcasting โ€” Implicit assignment of a child object to a parent reference; always safe; restricts accessible members to the parent type.
  • Downcasting โ€” Explicit cast of a parent reference back to a child type; requires (ChildType) syntax; checked by JVM at runtime.
  • ClassCastException โ€” Runtime exception thrown when a downcast fails because the actual object is not an instance of the target type.
  • instanceof โ€” Operator that returns true if an object is an instance of the specified type or any subtype; returns false for null.
  • Pattern matching instanceof (Java 16) โ€” Combined type check and cast: if (x instanceof Dog d) binds d as a Dog if the check passes.
  • getClass() โ€” Returns the exact runtime class of an object; unlike instanceof, does not check supertypes.
TOPIC 23

Abstraction

๐ŸŽญ What is Abstraction?

Abstraction means hiding the implementation details and showing only the essential features to the outside world. It answers: "What does this do?" โ€” not "How does it do it?". You use a car without knowing how the engine works; you call list.sort() without knowing the sorting algorithm.

๐Ÿ“Œ Abstraction vs Encapsulation โ€” the classic confusion
Abstraction: hides complexity / implementation โ€” exposes only the relevant interface. "What it does." Achieved via abstract classes and interfaces.

Encapsulation: hides data / state โ€” bundles fields with methods and controls access. "How its data is protected." Achieved via private fields + getters/setters.

A class can be both: fields are encapsulated (private), behaviour is abstracted (method signature visible, body hidden in subclass).

๐Ÿ—๏ธ Abstract Class

An abstract class is declared with the abstract keyword. It can have abstract methods (signature only, no body) and concrete methods (with a body). It cannot be instantiated directly โ€” you must create a concrete subclass that implements all abstract methods.

ABSTRACT CLASS โ€” ANATOMY โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ abstract class Shape { String color; // concrete field Shape(String color) { // concrete constructor this.color = color; // (called via super() from child) } abstract double area(); // abstract method โ€” no body abstract double perimeter(); // abstract method โ€” no body void describe() { // concrete method โ€” has body System.out.println(getClass().getSimpleName() + " | color:" + color + " | area:" + area()); // calls abstract method (DMD) } } // new Shape("red"); โ† COMPILE ERROR โ€” cannot instantiate abstract class class Circle extends Shape { double r; Circle(String c, double r) { super(c); this.r = r; } @Override double area() { return Math.PI * r * r; } @Override double perimeter() { return 2 * Math.PI * r; } // Must implement ALL abstract methods or be abstract itself } โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

๐Ÿ“‹ Abstract Class Rules

RuleDetail
Declared withabstract keyword before class
InstantiationCannot be instantiated โ€” compile error on new AbstractClass()
Abstract methodsCan have zero or more โ€” no body, ends with semicolon
Concrete methodsCan have any number of fully implemented methods
Fields & constructorsCan have both โ€” constructor called via super() from subclass
Subclass contractConcrete subclass MUST implement ALL abstract methods โ€” or be abstract itself
Access modifiersAbstract methods can be public, protected, or default โ€” NOT private
static abstractNOT allowed โ€” abstract methods need overriding; static cannot be overridden
final abstractNOT allowed โ€” contradiction (final cannot be overridden; abstract must be)

๐Ÿ”‘ Abstract Class vs Interface โ€” Quick Comparison

Abstract ClassInterface
Keywordabstract classinterface
Multiple inheritanceOnly one (extends)Multiple (implements)
FieldsAny type (instance + static)Only public static final
ConstructorYes (called via super)No
Access modifiersAny modifier on membersAll methods implicitly public
Use whenShared state + partial implementation + IS-APure contract, multiple types

๐ŸŽฏ When to Use Abstract Class

USE ABSTRACT CLASS WHEN: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โœ… You want to share STATE (fields) among related classes โœ… You want to provide some DEFAULT behaviour (concrete methods) that subclasses inherit unchanged โœ… You want a TEMPLATE (Template Method Pattern) โ€” define the skeleton of an algorithm in the abstract class, let subclasses fill in the steps โœ… The IS-A relationship is genuine (Dog IS-A Animal) โœ… You want to force subclasses to implement specific methods Use INTERFACE when you just need a contract with no shared state. Use ABSTRACT CLASS when there is genuine shared implementation.

๐Ÿ“ Template Method Pattern

One of the most important design patterns, enabled directly by abstract classes. The abstract class defines a final template method that calls abstract "hook" methods. Subclasses implement the hooks but cannot change the overall algorithm skeleton.

TEMPLATE METHOD PATTERN โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ abstract class DataProcessor { // Template method โ€” the algorithm skeleton (final = locked) final void process() { readData(); // abstract hook processData(); // abstract hook writeData(); // abstract hook } abstract void readData(); abstract void processData(); abstract void writeData(); } class CsvProcessor extends DataProcessor { void readData() { /* read CSV */ } void processData() { /* parse CSV */ } void writeData() { /* write output */ } } // process() skeleton never changes across implementations

๐Ÿ’ป Program 1 โ€” Abstract Shape Hierarchy

AbstractShape.java
abstract class Shape {
    protected String color;

    Shape(String color) { this.color = color; }

    // Abstract methods โ€” subclasses MUST implement
    abstract double area();
    abstract double perimeter();

    // Concrete method โ€” shared behaviour, calls abstract via DMD
    void describe() {
        System.out.printf("%-12s color:%-7s area:%8.3f  perim:%7.3f%n",
            getClass().getSimpleName(), color, area(), perimeter());
    }
}

class Circle extends Shape {
    private double r;
    Circle(String c, double r) { super(c); this.r = r; }
    @Override public double area()      { return Math.PI * r * r; }
    @Override public double perimeter() { return 2 * Math.PI * r; }
}

class Rectangle extends Shape {
    private double w, h;
    Rectangle(String c, double w, double h) {
        super(c); this.w=w; this.h=h;
    }
    @Override public double area()      { return w * h; }
    @Override public double perimeter() { return 2 * (w + h); }
}

class Triangle extends Shape {
    private double a, b, c;
    Triangle(String col, double a, double b, double c) {
        super(col); this.a=a; this.b=b; this.c=c;
    }
    @Override public double area() {
        double s = (a+b+c)/2;
        return Math.sqrt(s*(s-a)*(s-b)*(s-c));
    }
    @Override public double perimeter() { return a+b+c; }
}

public class AbstractShape {
    public static void main(String[] args) {
        // new Shape("red");  โ† compile error: cannot instantiate abstract

        Shape[] shapes = {
            new Circle   ("Red",    7),
            new Rectangle("Blue",   4, 6),
            new Triangle ("Green",  3, 4, 5)
        };

        double totalArea = 0;
        for (Shape s : shapes) {
            s.describe();        // concrete method calls abstract area()/perimeter() via DMD
            totalArea += s.area();
        }
        System.out.printf("Total area: %.3f%n", totalArea);
    }
}
โ–ถ OUTPUT
Circle color:Red area: 153.938 perim: 43.982 Rectangle color:Blue area: 24.000 perim: 20.000 Triangle color:Green area: 6.000 perim: 12.000 Total area: 183.938

๐Ÿ’ป Program 2 โ€” Template Method Pattern

TemplateMethod.java
// Abstract class defines the SKELETON โ€” subclasses fill the STEPS
abstract class ReportGenerator {

    // Template method โ€” final so no subclass can change the order
    final void generate() {
        fetchData();
        processData();
        formatReport();
        exportReport();
    }

    abstract void fetchData();      // step 1 โ€” subclass decides source
    abstract void processData();    // step 2 โ€” subclass decides logic
    abstract void formatReport();   // step 3 โ€” subclass decides format

    void exportReport() {           // concrete โ€” default export
        System.out.println("[EXPORT] Saved to /reports/");
    }
}

class PdfReportGenerator extends ReportGenerator {
    @Override void fetchData()     { System.out.println("[PDF] Fetching from DB");       }
    @Override void processData()   { System.out.println("[PDF] Aggregating rows");       }
    @Override void formatReport()  { System.out.println("[PDF] Applying PDF layout");    }
}

class ExcelReportGenerator extends ReportGenerator {
    @Override void fetchData()     { System.out.println("[XLS] Fetching from REST API");  }
    @Override void processData()   { System.out.println("[XLS] Pivoting data");          }
    @Override void formatReport()  { System.out.println("[XLS] Applying cell styles");   }
    @Override void exportReport()  { System.out.println("[XLS] Emailing the file");       }
}

public class TemplateMethod {
    public static void main(String[] args) {
        System.out.println("=== PDF Report ===");
        new PdfReportGenerator().generate();

        System.out.println("=== Excel Report ===");
        new ExcelReportGenerator().generate();
    }
}
โ–ถ OUTPUT
=== PDF Report === [PDF] Fetching from DB [PDF] Aggregating rows [PDF] Applying PDF layout [EXPORT] Saved to /reports/ === Excel Report === [XLS] Fetching from REST API [XLS] Pivoting data [XLS] Applying cell styles [XLS] Emailing the file

๐Ÿ’ป Program 3 โ€” Abstract Class with Constructor & Partial Implementation

AbstractEmployee.java
abstract class Employee {
    protected String name;
    protected double baseSalary;

    // Abstract class CAN have a constructor (called via super)
    Employee(String name, double baseSalary) {
        this.name       = name;
        this.baseSalary = baseSalary;
    }

    // Abstract โ€” each employee type calculates bonus differently
    abstract double calculateBonus();

    // Concrete โ€” shared total salary logic
    double totalSalary() { return baseSalary + calculateBonus(); }

    void printPayslip() {
        System.out.printf("%-15s | Base:%8.2f | Bonus:%7.2f | Total:%8.2f%n",
            name, baseSalary, calculateBonus(), totalSalary());
    }
}

class Manager extends Employee {
    int teamSize;
    Manager(String n, double b, int t) { super(n,b); teamSize=t; }
    @Override
    double calculateBonus() { return baseSalary * 0.20 + teamSize * 500; }
}

class Developer extends Employee {
    String level;  // "junior" | "senior"
    Developer(String n, double b, String l) { super(n,b); level=l; }
    @Override
    double calculateBonus() {
        return level.equals("senior") ? baseSalary * 0.15 : baseSalary * 0.05;
    }
}

class Intern extends Employee {
    Intern(String n, double b) { super(n,b); }
    @Override
    double calculateBonus() { return 0; }   // no bonus
}

public class AbstractEmployee {
    public static void main(String[] args) {
        Employee[] staff = {
            new Manager  ("Anita Sharma",  80000, 5),
            new Developer("Rohan Verma",   60000, "senior"),
            new Developer("Priya Singh",   40000, "junior"),
            new Intern   ("Arjun Mehta",   15000)
        };
        for (Employee e : staff) e.printPayslip();
    }
}
โ–ถ OUTPUT
Anita Sharma | Base:80000.00 | Bonus:18500.00 | Total:98500.00 Rohan Verma | Base:60000.00 | Bonus: 9000.00 | Total:69000.00 Priya Singh | Base:40000.00 | Bonus: 2000.00 | Total:42000.00 Arjun Mehta | Base:15000.00 | Bonus: 0.00 | Total:15000.00
๐Ÿ’ก Abstract class with 0 abstract methods โ€” still valid!
A class declared abstract with no abstract methods is perfectly legal. It simply cannot be instantiated. This is useful when you want to prevent direct instantiation of a base class while still providing concrete utility methods โ€” forcing callers to use a specific subclass.
โญ Must-Know Interview Topics
Abstraction definition ยท abstract class rules (cannot instantiate, can have constructor/fields) ยท abstract method rules (no body, not private/static/final) ยท abstract class vs interface ยท Template Method pattern ยท 0 abstract methods still valid ยท concrete subclass must implement all abstract methods
Q1. What is abstraction? How is it achieved in Java?
Abstraction means hiding implementation details and exposing only the essential interface โ€” showing what an object does, not how it does it.

In Java, abstraction is achieved through two mechanisms:
1. Abstract classes โ€” partially abstract; can mix abstract methods (no body) with concrete methods (with body). Use when sharing state and partial implementation among related types.
2. Interfaces โ€” fully abstract contract (before Java 8); use when defining a pure contract that unrelated types can implement. Covered fully in Topics 25โ€“26.
Q2. Can we instantiate an abstract class?
No โ€” directly instantiating an abstract class is a compile error. new Shape() where Shape is abstract โ†’ "Shape is abstract; cannot be instantiated".

However, you can:
โ€” Create an instance of a concrete subclass and assign it to an abstract class reference: Shape s = new Circle("red", 5);
โ€” Create an anonymous subclass inline: Shape s = new Shape("red") { double area() { return 0; } double perimeter() { return 0; } }; โ€” this is a one-time concrete subclass defined anonymously.
Q3. Can an abstract class have a constructor? What is it used for?
Yes โ€” abstract classes can (and usually should) have constructors. The constructor is not used to create instances of the abstract class directly (that is blocked). Instead, it is called via super() from the concrete subclass constructor to initialise the shared fields defined in the abstract class.

Example: abstract class Employee { Employee(String name, double salary) { this.name = name; } }
class Manager extends Employee { Manager(String n, double s) { super(n, s); } }

Without a constructor in the abstract class, every subclass would have to initialise those shared fields itself โ€” defeating the purpose of the shared base class.
Q4. What are the rules for abstract methods?
An abstract method:
โ€” Has no body โ€” ends with a semicolon: abstract double area();
โ€” Must be declared inside an abstract class or interface
โ€” Cannot be private (private means not inherited, but abstract requires overriding)
โ€” Cannot be static (static methods cannot be overridden)
โ€” Cannot be final (final cannot be overridden, but abstract must be)
โ€” Every concrete subclass must provide an implementation, or itself be declared abstract
Q5. What is the difference between an abstract class and an interface?
Abstract class: can have instance fields, constructors, concrete methods, and abstract methods. A class can extend only one abstract class. Best for IS-A relationships with shared state and partial implementation.

Interface: fields are implicitly public static final (constants). No instance fields. No constructors. Methods are implicitly public abstract (pre-Java 8); can have default and static methods (Java 8+). A class can implement multiple interfaces. Best for defining a pure contract that unrelated types can fulfil.

Rule of thumb: if related classes share state and behaviour โ†’ abstract class. If unrelated types need to fulfil a contract โ†’ interface.
Q6. What is the Template Method design pattern?
The Template Method pattern defines the skeleton of an algorithm in a final method in the abstract class, deferring specific steps to abstract methods that subclasses implement.

The abstract class controls the flow (the template method is final and cannot be overridden). Subclasses customise individual steps without changing the overall structure.

Real-world uses: Spring's JdbcTemplate (fixed DB operation skeleton, customisable query/mapping), AbstractHealthIndicator in Spring Actuator (template calls doHealthCheck() which subclasses implement), servlet lifecycle (HttpServlet.service() calls doGet()/doPost() which subclasses override).

๐ŸŒฑ Abstract Classes in Spring Boot

Spring Boot uses abstract classes extensively in its own framework code, and you write abstract base classes for shared service/controller logic.

โœ… Common abstract class patterns in Spring Boot
Abstract base service โ€” shared validation, logging, and CRUD scaffolding in an abstract class; concrete services extend it
AbstractHealthIndicator โ€” Spring Actuator base; override doHealthCheck() to add custom health checks
AbstractMessageConverterMethodArgumentResolver โ€” Spring MVC internals
Abstract integration test base โ€” common @SpringBootTest setup in an abstract class; test classes extend it
Abstract scheduled job โ€” shared logging/metrics in abstract base; concrete jobs implement the work method
AbstractCrudService.java + ProductService.java โ€” Spring Boot
// Abstract base service โ€” template method pattern in Spring
public abstract class AbstractCrudService<T, ID> {

    // Template method โ€” fixed workflow, subclass fills the steps
    public final T create(T entity) {
        validate(entity);          // abstract โ€” subclass validates
        beforeSave(entity);        // hook โ€” optional override
        T saved = save(entity);    // abstract โ€” subclass persists
        afterSave(saved);          // hook โ€” optional override
        return saved;
    }

    protected abstract void validate(T entity);
    protected abstract    T save(T entity);

    // Hooks โ€” concrete no-ops by default; subclass may override
    protected void beforeSave(T entity) {}
    protected void afterSave (T saved)  {}
}

@Service
public class ProductService
        extends AbstractCrudService<Product, Long> {

    private final ProductRepository repo;
    public ProductService(ProductRepository r) { this.repo = r; }

    @Override
    protected void validate(Product p) {
        if (p.getPrice() <= 0)
            throw new IllegalArgumentException("Price must be positive");
    }

    @Override
    protected Product save(Product p) { return repo.save(p); }

    @Override
    protected void afterSave(Product p) {
        System.out.println("Product saved: " + p.getName());
    }
}
๐Ÿ“Œ AbstractHealthIndicator โ€” Spring Actuator template method in action
Spring Actuator's AbstractHealthIndicator is a textbook template method pattern. You extend it and implement doHealthCheck(Health.Builder builder). The abstract class handles the try-catch, status propagation, and integration with the Actuator health endpoint. Your subclass just fills in the check logic โ€” the infrastructure is handled for you.
โญ Must-Know Exam Points
  • Abstract class declared with abstract keyword
  • Cannot instantiate abstract class โ€” compile error
  • Abstract class CAN have: constructor, fields, concrete methods, static methods
  • Abstract method: no body, ends with ;, not private/static/final
  • Concrete subclass must implement ALL abstract methods โ€” or itself be abstract
  • Abstract class with 0 abstract methods is valid โ€” just blocks instantiation
  • abstract + final on same class โ†’ compile error (contradiction)
  • abstract + private on same method โ†’ compile error
  • abstract + static on same method โ†’ compile error
  • Template Method Pattern = abstract class defines skeleton + subclasses fill steps

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Can abstract class have concrete methods?YesIt can mix abstract and concrete methods freely
Can abstract class have a constructor?YesCalled via super() from subclass โ€” not for direct instantiation
abstract + final class โ€” valid?No โ€” compile errorfinal blocks subclassing; abstract requires it โ€” direct contradiction
abstract + private method โ€” valid?No โ€” compile errorprivate not inherited; abstract requires overriding โ€” contradiction
Abstract class with 0 abstract methods โ€” valid?YesStill cannot be instantiated; forces use of subclass
Concrete subclass doesn't implement one abstract method โ€” valid?No โ€” unless subclass is also abstractEvery concrete subclass must implement ALL abstract methods
Can abstract method be static?No โ€” compile errorstatic methods cannot be overridden โ€” no-op for abstract

๐Ÿ“– One-Liner Definitions

  • Abstraction โ€” OOP principle of hiding implementation details and exposing only the essential interface; achieved via abstract classes and interfaces.
  • Abstract class โ€” A class declared with abstract; cannot be instantiated; may contain abstract and concrete members.
  • Abstract method โ€” A method with no body, declared with abstract; must be implemented by every concrete subclass.
  • Concrete subclass โ€” A non-abstract subclass that provides implementations for all inherited abstract methods.
  • Template Method pattern โ€” A design pattern where an abstract class defines a fixed algorithm skeleton in a final method, and subclasses implement the variable steps via abstract methods.
  • Anonymous subclass โ€” An inline, unnamed implementation of an abstract class or interface, defined with new AbstractClass() { ... }.
TOPIC 24

Inner Classes

๐Ÿช† What is an Inner Class?

An inner class (nested class) is a class defined inside another class. Java supports four kinds. Each has a different relationship with the enclosing class and different rules for instantiation and member access.

FOUR TYPES OF NESTED CLASSES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Outer { class Inner { } // 1. Non-static inner class // (member inner class) static class StaticNested { } // 2. Static nested class void method() { class LocalClass { } // 3. Local class (inside method) } void demo() { Runnable r = new Runnable() { // 4. Anonymous inner class public void run() { } // (no name, declared + used once) }; } } โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

1๏ธโƒฃ Non-Static Inner Class (Member Inner Class)

Defined as a member of the outer class without the static keyword. It is associated with an instance of the outer class โ€” it can directly access all instance and static members of the outer class, including private ones.

NON-STATIC INNER CLASS โ€” KEY RULES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Engine { // Outer private int horsepower = 200; class Piston { // Non-static inner void fire() { System.out.println(horsepower); // โœ… accesses outer private! } } } Instantiation โ€” requires an OUTER instance first: Engine eng = new Engine(); Engine.Piston p = eng.new Piston(); // โ† syntax: outerRef.new Inner() Rules: โœ… Can access ALL outer members (even private) โœ… Has implicit reference to outer: Engine.this โŒ Cannot declare static members (except static final constants) โŒ Cannot be instantiated without an outer instance

2๏ธโƒฃ Static Nested Class

Declared with static inside an outer class. It is NOT associated with any outer instance. It can only access the static members of the outer class directly. It behaves like a regular top-level class that happens to live in the outer class namespace.

STATIC NESTED CLASS โ€” KEY RULES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class University { private static String name = "MIT"; private int ranking = 1; // instance field static class Department { void display() { System.out.println(name); // โœ… static outer member โ€” OK // System.out.println(ranking); โŒ instance member โ€” NOT OK } } } Instantiation โ€” NO outer instance needed: University.Department dept = new University.Department(); Rules: โœ… Can access static members of outer class โœ… Can declare static members itself โœ… Does NOT need an outer instance to be created โŒ Cannot access instance members of outer class directly

3๏ธโƒฃ Local Class

Defined inside a method body. It is only visible within that method. It can access the method's local variables if they are final or effectively final.

LOCAL CLASS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ void processOrder(String type) { final String prefix = "ORDER"; // effectively final class Formatter { // local class โ€” only exists here String format(int id) { return prefix + "-" + type + "-" + id; } } Formatter f = new Formatter(); // instantiated inside same method System.out.println(f.format(42)); } // Formatter does NOT exist outside processOrder() โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

4๏ธโƒฃ Anonymous Inner Class

A class declared and instantiated in a single expression with no name. It extends a class or implements an interface inline. Widely used before Java 8 lambdas โ€” now partially replaced by lambdas, but still required for multi-method interfaces and abstract classes.

ANONYMOUS INNER CLASS โ€” SYNTAX โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ // Extending an abstract class anonymously: Shape s = new Shape("red") { // Shape is abstract double area() { return 0; } double perimeter() { return 0; } }; // Implementing an interface anonymously: Comparator byLength = new Comparator() { public int compare(String a, String b) { return a.length() - b.length(); } }; // With Runnable (pre-lambda style): Thread t = new Thread(new Runnable() { public void run() { System.out.println("Running"); } }); Rules: โœ… Can extend one class OR implement one interface โœ… Can access effectively final local variables from enclosing scope โŒ Cannot have a constructor (no name to give it) โŒ Cannot extend a class AND implement interface simultaneously โŒ Cannot have static members (except static final constants)
๐Ÿ“Œ Anonymous class vs Lambda
Anonymous classes are still needed when: (1) the interface has more than one abstract method, (2) you are subclassing an abstract class, (3) you need to maintain state across multiple method calls within the anonymous object. For single-abstract-method (functional) interfaces like Runnable and Comparator, prefer lambdas โ€” they are more concise and don't create a separate .class file.

๐Ÿ“Š All Four Types โ€” Summary Table

Non-Static InnerStatic NestedLocalAnonymous
Needs outer instanceโœ… YesโŒ NoN/A (in method)N/A
Access outer instance membersโœ… YesโŒ Noโœ… Effectively finalโœ… Effectively final
Can have static membersโŒ No*โœ… YesโŒ NoโŒ No
Has a nameโœ… Yesโœ… Yesโœ… Yes (local)โŒ No
Generates separate .classOuter$Inner.classOuter$Nested.classOuter$1Local.classOuter$1.class

๐Ÿ’ป Program 1 โ€” Non-Static Inner Class & Static Nested Class

InnerClasses.java
class Car {
    private String brand;
    private int    year;
    private static int totalCars = 0;

    Car(String brand, int year) {
        this.brand = brand; this.year = year;
        totalCars++;
    }

    // 1. Non-static inner class โ€” tied to Car instance
    class Engine {
        private int horsepower;
        Engine(int hp) { this.horsepower = hp; }

        void start() {
            // Accesses outer class PRIVATE fields directly
            System.out.println("Engine of " + brand     // outer private
                + " (" + year + ") started โ€” "        // outer private
                + horsepower + " HP");
        }

        void showOuterRef() {
            // Explicit outer reference: Car.this
            System.out.println("Outer: " + Car.this.brand);
        }
    }

    // 2. Static nested class โ€” independent of any Car instance
    static class Factory {
        static Car makeSuv(String brand) {
            // Can access static outer members
            System.out.println("[Factory] Total cars so far: " + totalCars);
            return new Car(brand, 2024);
            // Cannot access: brand, year (instance members)
        }
    }
}

public class InnerClasses {
    public static void main(String[] args) {

        // Static nested โ€” no Car instance needed
        Car suv = Car.Factory.makeSuv("Toyota");

        // Non-static inner โ€” needs Car instance first
        Car car = new Car("Honda", 2023);
        Car.Engine eng = car.new Engine(150);   // outerRef.new InnerClass()
        eng.start();
        eng.showOuterRef();
    }
}
โ–ถ OUTPUT
[Factory] Total cars so far: 0 Engine of Honda (2023) started โ€” 150 HP Outer: Honda

๐Ÿ’ป Program 2 โ€” Anonymous Inner Class (Abstract Class & Interface)

AnonymousClass.java
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

abstract class Greeting {
    abstract void greet(String name);
    void farewell(String name) {
        System.out.println("Goodbye, " + name);
    }
}

public class AnonymousClass {
    public static void main(String[] args) {

        // 1. Anonymous class extending abstract class
        Greeting formal = new Greeting() {
            @Override
            public void greet(String name) {
                System.out.println("Good morning, " + name + ".");
            }
        };
        formal.greet("Dr. Sharma");
        formal.farewell("Dr. Sharma");    // inherited concrete method

        // 2. Anonymous class implementing interface
        Runnable task = new Runnable() {
            @Override
            public void run() {
                System.out.println("Task running in anonymous class");
            }
        };
        task.run();

        // 3. Anonymous Comparator โ€” sorting with custom logic
        List<String> names = Arrays.asList("Rohan", "Asha", "Priya", "Ali");
        names.sort(new Comparator<String>() {
            @Override
            public int compare(String a, String b) {
                return a.length() - b.length();  // sort by length
            }
        });
        System.out.println("Sorted by length: " + names);

        // Lambda equivalent (Java 8+) โ€” replaces single-method anonymous class
        names.sort((a, b) -> a.compareTo(b));
        System.out.println("Sorted alphabetically: " + names);
    }
}
โ–ถ OUTPUT
Good morning, Dr. Sharma. Goodbye, Dr. Sharma Task running in anonymous class Sorted by length: [Ali, Asha, Rohan, Priya] Sorted alphabetically: [Ali, Asha, Priya, Rohan]

๐Ÿ’ป Program 3 โ€” Local Class & Outer Reference Access

LocalClassDemo.java
public class OrderProcessor {
    private String currency = "INR";

    void processOrders(int[] orderIds) {
        final String prefix = "ORD";      // effectively final local var

        // Local class โ€” only visible inside this method
        class OrderLabel {
            String format(int id, double amount) {
                // Accesses:  outer instance field (currency)
                //           + effectively final local var (prefix)
                return prefix + "-" + id + " [" + currency + " " + amount + "]";
            }
        }

        OrderLabel label = new OrderLabel();
        double amount = 1000.0;
        for (int id : orderIds) {
            System.out.println(label.format(id, amount));
            amount += 500;
        }
        // OrderLabel cannot be used outside this method
    }

    public static void main(String[] args) {
        new OrderProcessor().processOrders(new int[]{101, 102, 103});
    }
}
โ–ถ OUTPUT
ORD-101 [INR 1000.0] ORD-102 [INR 1500.0] ORD-103 [INR 2000.0]
โš ๏ธ Inner class memory leak risk
A non-static inner class always holds an implicit reference to its outer class instance. If an inner class object outlives the outer object (e.g., is stored in a long-lived list or passed to another thread), the outer class instance cannot be garbage collected. Use a static nested class when the inner class does not need access to outer instance members โ€” this eliminates the implicit outer reference.
โญ Must-Know Interview Topics
Four types of inner classes ยท non-static vs static nested instantiation syntax ยท anonymous class vs lambda ยท local class access rules (effectively final) ยท outer reference (OuterClass.this) ยท inner class memory leak ยท when to use each type
Q1. What are the four types of nested classes in Java?
1. Non-static inner class (member inner class): declared as an instance member of the outer class; tied to an outer instance; can access all outer members including private; instantiated with outerRef.new Inner().

2. Static nested class: declared with static inside the outer class; not tied to any outer instance; can only access static outer members; instantiated with new Outer.Nested().

3. Local class: declared inside a method body; visible only within that method; can access effectively final local variables.

4. Anonymous inner class: unnamed class declared and instantiated in a single expression; extends a class or implements an interface inline; cannot have a constructor; partially replaced by lambdas for single-method interfaces.
Q2. How do you instantiate a non-static inner class vs a static nested class?
Non-static inner class: requires an outer class instance first, then uses the special syntax outerRef.new Inner():
Car car = new Car("Honda", 2023);
Car.Engine engine = car.new Engine(150);

Static nested class: does not need an outer instance; instantiated like a regular class but with the outer class as a namespace qualifier:
Car.Factory factory = new Car.Factory(); or Car car = Car.Factory.makeSuv("Toyota");

The key difference: non-static inner needs outerInstance.new; static nested uses new Outer.Nested().
Q3. What is the difference between an anonymous class and a lambda?
Anonymous class: can implement interfaces with any number of methods; can extend an abstract class; can maintain state with instance fields; can call methods from the enclosing scope.

Lambda: only for functional interfaces (exactly one abstract method); more concise; does not create a new scope for this (this inside a lambda refers to the enclosing class); does not generate a separate .class file at compile time (uses invokedynamic bytecode).

Use anonymous class when: implementing a multi-method interface, subclassing an abstract class, or needing per-instance state. Use lambda for functional interfaces โ€” it is cleaner and more performant.
Q4. Why can a local/anonymous class only access final or effectively final variables?
Local and anonymous classes can outlive the method invocation they are defined in (e.g., they can be passed to another thread). If they could capture a mutable local variable, the variable on the stack would be gone by the time the class instance uses it โ€” causing inconsistency.

Java's solution: the class gets a copy of the captured variable's value. For this copy to be meaningful, the variable must never change โ€” it must be final or effectively final. If you try to reassign a captured variable, the compiler rejects it with: "local variable used in lambda expression must be final or effectively final".
Q5. How does a non-static inner class access the outer class instance?
A non-static inner class always holds an implicit reference to the outer class instance. This reference is automatically available as OuterClass.this.

You need OuterClass.this explicitly only when there is a name conflict โ€” for example, when both the inner class and outer class have a field called name. Inside the inner class, name refers to the inner field; Car.this.name explicitly refers to the outer field.

Normally, outer members are accessed directly (no prefix needed) because the compiler resolves them via the implicit outer reference automatically.
Q6. Can a non-static inner class have static members?
Not in general โ€” a non-static inner class cannot declare static fields or static methods (compile error) because it is instance-dependent. The exception is static final constants (compile-time constants): static final int MAX = 100; is allowed inside a non-static inner class because the value is inlined by the compiler and does not actually require a class-level context.

If you need a nested class with static members, use a static nested class instead.

๐ŸŒฑ Inner Classes in Spring Boot

Inner classes appear in Spring Boot in configuration, test utilities, builder patterns, and event/DTO design.

โœ… Common inner class patterns in Spring Boot
Static nested DTO/Builder โ€” Product.Builder or ApiResponse.Error as static nested classes for clean namespacing
Static nested @Configuration โ€” inner @Configuration classes inside a parent config for grouping related beans
Anonymous Comparator / Callable โ€” pre-Java-8 style; now mostly replaced by lambdas
Test base class โ€” abstract test class with @SpringBootTest; test classes extend it
Static nested exception classes โ€” domain-specific exceptions as static nested classes of a service
ApiResponse.java โ€” static nested class pattern in Spring Boot REST
// Clean REST response wrapper using static nested classes
public class ApiResponse<T> {
    private final boolean success;
    private final       T data;
    private final  Error error;

    private ApiResponse(boolean s, T d, Error e) {
        success=s; data=d; error=e;
    }

    public static <T> ApiResponse<T> ok(T data) {
        return new ApiResponse<>(true, data, null);
    }
    public static <T> ApiResponse<T> fail(String code, String msg) {
        return new ApiResponse<>(false, null, new Error(code, msg));
    }

    // Static nested class โ€” no outer instance needed, clean namespace
    public static class Error {
        private final String code, message;
        Error(String code, String msg) {
            this.code=code; this.message=msg;
        }
        public String getCode   () { return code; }
        public String getMessage() { return message; }
    }

    // Getters omitted for brevity
}

// Usage in controller:
// return ResponseEntity.ok(ApiResponse.ok(product));
// return ResponseEntity.badRequest().body(ApiResponse.fail("NOT_FOUND", "..."));
๐Ÿ“Œ Use static nested, not non-static inner, in Spring beans
Non-static inner classes inside Spring beans would hold a reference to the bean instance, causing memory issues. Always use static nested classes for DTOs, builders, error types, and configuration helpers inside Spring components. Static nested classes do not carry the outer bean reference and are GC-friendly.
โญ Must-Know Exam Points
  • Non-static inner: instance-tied, accesses all outer members, instantiated with outer.new Inner()
  • Static nested: no outer instance needed, accesses only outer static members, instantiated with new Outer.Nested()
  • Local class: defined inside method, visible only there, captures effectively final vars
  • Anonymous class: no name, declared + instantiated once, extends class OR implements interface
  • Non-static inner class cannot have static members (except static final constants)
  • Anonymous class cannot have a constructor
  • Anonymous class vs lambda: lambda only for single-abstract-method (functional) interfaces
  • OuterClass.this โ€” explicit outer reference from inside inner class
  • Non-static inner class holds implicit outer reference โ†’ potential memory leak
  • Each inner class generates a separate .class file at compile time

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Can non-static inner class access private outer fields?YesInner class has full access to all outer members including private
Can static nested class access outer instance fields?NoNo outer instance โ€” only static outer members accessible
Anonymous class implements interface with 2 methods โ€” compile error?No โ€” validAnonymous classes can implement multi-method interfaces; lambdas cannot
Anonymous class can have a constructor?NoNo name = no constructor; initialise with instance initialiser block instead
Local class can access non-final local variable?No โ€” compile errorMust be final or effectively final; compiler copies the value
How to instantiate non-static inner: new Outer.Inner()?Wrong โ€” compile errorCorrect syntax: outerInstance.new Inner()
this inside anonymous class refers to?The anonymous class instanceUnlike lambda, anonymous class creates its own scope; this = anonymous object

๐Ÿ“– One-Liner Definitions

  • Non-static inner class โ€” A class defined as an instance member of another class; holds an implicit reference to the outer instance; can access all outer members.
  • Static nested class โ€” A class declared with static inside another class; independent of any outer instance; accesses only outer static members.
  • Local class โ€” A class defined inside a method body; scoped to that method; can capture effectively final local variables.
  • Anonymous inner class โ€” An unnamed class declared and instantiated as a single expression; extends one class or implements one interface; has no constructor.
  • Effectively final โ€” A local variable that is never reassigned after initialisation; can be captured by local/anonymous classes and lambdas.
  • OuterClass.this โ€” The explicit syntax used inside a non-static inner class to refer to the enclosing outer class instance.
TOPIC 25

Interfaces โ€” Fundamentals

๐Ÿ”Œ What is an Interface?

An interface is a pure contract โ€” it defines what a class must do, without saying how. Any class that implements an interface must provide a concrete body for every abstract method in that interface. Interfaces achieve full abstraction (before Java 8) and are the primary vehicle for multiple inheritance of type in Java.

INTERFACE โ€” THE CONTRACT METAPHOR โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Interface = a legally binding contract Implementing class = a party that signs and honours the contract interface Printable { void print(); // contract clause โ€” "you MUST implement this" } class Document implements Printable { public void print() { // honouring the contract System.out.println("Printing document..."); } } Key difference from abstract class: Abstract class โ†’ IS-A relationship (Dog IS-A Animal) Interface โ†’ CAN-DO capability (Dog CAN-DO Swim, Fetch)

๐Ÿ“‹ Interface Syntax & Default Modifiers

Members of an interface have implicit (compiler-added) modifiers. You can write them explicitly, but they are redundant.

DEFAULT MODIFIERS IN AN INTERFACE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ interface Vehicle { // Methods โ†’ implicitly public abstract void start(); // = public abstract void start() void stop(); // = public abstract void stop() // Fields โ†’ implicitly public static final (constants) int MAX_SPEED = 200; // = public static final int MAX_SPEED = 200 String FUEL = "Petrol"; // = public static final String FUEL = "Petrol" } // Must provide ALL abstract methods or be abstract itself class Car implements Vehicle { public void start() { System.out.println("Car starts"); } public void stop() { System.out.println("Car stops"); } }
โš ๏ธ Interface method implementation must be public
When implementing an interface method in a class, you must declare it public. The interface declares it as public abstract; any implementation that reduces visibility (e.g., to package-private) would violate the contract and causes a compile error. This is a very common exam trap.

๐Ÿ”— Multiple Interface Implementation

A class can implement multiple interfaces โ€” this is how Java achieves multiple inheritance of type without the Diamond Problem that plagued multiple class inheritance.

MULTIPLE INTERFACE IMPLEMENTATION โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ interface Flyable { void fly(); } interface Swimmable{ void swim(); } interface Runnable { void run(); } class Duck implements Flyable, Swimmable, Runnable { public void fly() { System.out.println("Duck flies"); } public void swim() { System.out.println("Duck swims"); } public void run() { System.out.println("Duck runs"); } } // A Duck IS-A Flyable, IS-A Swimmable, IS-A Runnable // All three reference types can hold a Duck: Flyable f = new Duck(); Swimmable s = new Duck(); Runnable r = new Duck(); // Class hierarchy + interface together: class FlyingCar extends Car implements Flyable, Chargeable { ... } // โ†’ single extends, multiple implements

๐Ÿ”„ Interface Reference Type (Polymorphism)

An interface acts as a reference type. You can hold any implementing object in an interface variable โ€” full runtime polymorphism applies.

INTERFACE AS REFERENCE TYPE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ interface Shape { double area(); } class Circle implements Shape { public double area() { return Math.PI*r*r; } } class Rectangle implements Shape { public double area() { return w*h; } } class Triangle implements Shape { public double area() { return 0.5*b*h; } } Shape[] shapes = { new Circle(5), new Rectangle(4,6), new Triangle(3,8) }; double total = 0; for (Shape s : shapes) total += s.area(); // DMD โ€” each calls its own area() System.out.println("Total: " + total); The calling code never needs to know which concrete Shape it has. Adding a Pentagon class later: zero changes to this loop!

๐Ÿ†š Interface vs Abstract Class โ€” When to Use Which

FeatureInterfaceAbstract Class
Keywordinterface / implementsabstract class / extends
Multiple inheritanceโœ… implement manyโŒ extend only one
ConstructorโŒ noneโœ… yes
Instance fieldsโŒ only constantsโœ… any fields
Concrete methodsOnly default/static (Java 8+)โœ… freely
Access modifiers on methodsimplicitly publicany modifier
RelationshipCAN-DO capabilityIS-A relationship
Use whenUnrelated classes need same capabilityRelated classes share state + behaviour
๐Ÿ’ก Prefer interfaces over abstract classes in modern Java
With Java 8 default methods, interfaces can now provide method implementations too. Modern design favours interfaces: they allow multiple inheritance of type, are easier to mock in tests, and decouple code more effectively. Use abstract classes only when you genuinely need shared state (instance fields) or a protected/package-private contract.

๐Ÿ’ป Program 1 โ€” Interface Basics: Vehicle Contract

InterfaceBasics.java
interface Vehicle {
    // implicitly public static final
    int    MAX_SPEED = 200;
    String FUEL_TYPE = "Petrol";

    // implicitly public abstract
    void start();
    void stop();
    int  getSpeed();
}

interface Electric {
    void charge();
    int  getBatteryLevel();
}

class PetrolCar implements Vehicle {
    private int speed = 0;
    public void start()    { speed = 80;  System.out.println("Petrol car started"); }
    public void stop()     { speed = 0;   System.out.println("Petrol car stopped"); }
    public int  getSpeed() { return speed; }
}

// Implements TWO interfaces โ€” multiple inheritance of type
class ElectricCar implements Vehicle, Electric {
    private int speed   = 0;
    private int battery = 100;
    public void start()          { speed = 120; System.out.println("EV started silently"); }
    public void stop()           { speed = 0;   System.out.println("EV stopped"); }
    public int  getSpeed()       { return speed; }
    public void charge()         { battery = 100; System.out.println("Charging to 100%"); }
    public int  getBatteryLevel() { return battery; }
}

public class InterfaceBasics {
    static void testDrive(Vehicle v) {    // accepts ANY Vehicle
        v.start();
        System.out.println("Speed: " + v.getSpeed() + " | Max: " + Vehicle.MAX_SPEED);
        v.stop();
    }

    public static void main(String[] args) {
        testDrive(new PetrolCar());
        testDrive(new ElectricCar());

        // Multiple reference types for ElectricCar
        ElectricCar ev = new ElectricCar();
        Vehicle  vRef = ev;    // upcast to Vehicle
        Electric eRef = ev;    // upcast to Electric
        eRef.charge();
        System.out.println("Battery: " + eRef.getBatteryLevel() + "%");
    }
}
โ–ถ OUTPUT
Petrol car started Speed: 80 | Max: 200 Petrol car stopped EV started silently Speed: 120 | Max: 200 EV stopped Charging to 100% Battery: 100%

๐Ÿ’ป Program 2 โ€” Interface Polymorphism: Payment Gateway

PaymentGateway.java
interface PaymentGateway {
    boolean processPayment(double amount);
    String  getGatewayName();
    double  getTransactionFee(double amount);
}

class RazorpayGateway implements PaymentGateway {
    public boolean processPayment(double amt) {
        System.out.printf("  Razorpay: processing โ‚น%.2f%n", amt);
        return true;
    }
    public String getGatewayName()            { return "Razorpay"; }
    public double getTransactionFee(double a) { return a * 0.02; }  // 2%
}

class PayPalGateway implements PaymentGateway {
    public boolean processPayment(double amt) {
        System.out.printf("  PayPal: processing $%.2f%n", amt);
        return true;
    }
    public String getGatewayName()            { return "PayPal"; }
    public double getTransactionFee(double a) { return a * 0.029 + 0.30; } // 2.9% + $0.30
}

class Checkout {
    private final PaymentGateway gateway;    // interface ref โ€” loosely coupled

    Checkout(PaymentGateway gateway) { this.gateway = gateway; }

    void pay(double amount) {
        double fee   = gateway.getTransactionFee(amount);
        double total = amount + fee;
        System.out.printf("Gateway: %s | Base: %.2f | Fee: %.2f | Total: %.2f%n",
            gateway.getGatewayName(), amount, fee, total);
        gateway.processPayment(total);
    }
}

public class PaymentGatewayDemo {
    public static void main(String[] args) {
        new Checkout(new RazorpayGateway()).pay(1000);
        new Checkout(new PayPalGateway()).pay(1000);
    }
}
โ–ถ OUTPUT
Gateway: Razorpay | Base: 1000.00 | Fee: 20.00 | Total: 1020.00 Razorpay: processing โ‚น1020.00 Gateway: PayPal | Base: 1000.00 | Fee: 29.30 | Total: 1029.30 PayPal: processing $1029.30

๐Ÿ’ป Program 3 โ€” Interface Extending Interface

InterfaceInheritance.java
interface Readable  { String read();  }
interface Writable  { void   write(String data); }

// Interface can extend multiple interfaces
interface ReadWritable extends Readable, Writable {
    void flush();          // adds its own method
}

// Implementing class must honour ALL inherited + own methods
class FileStream implements ReadWritable {
    private String buffer = "";

    public String read()            { return "Reading: " + buffer; }
    public void   write(String d)   { buffer = d; System.out.println("Written: " + d); }
    public void   flush()            { buffer = ""; System.out.println("Buffer flushed"); }
}

public class InterfaceInheritance {
    public static void main(String[] args) {
        FileStream fs = new FileStream();
        fs.write("Hello, Java!");
        System.out.println(fs.read());
        fs.flush();
        System.out.println(fs.read());

        // Use as any parent interface reference โ€” polymorphism
        Readable     r = fs;
        Writable     w = fs;
        ReadWritable rw = fs;
        System.out.println("fs instanceof Readable: "     + (fs instanceof Readable));
        System.out.println("fs instanceof ReadWritable: " + (fs instanceof ReadWritable));
    }
}
โ–ถ OUTPUT
Written: Hello, Java! Reading: Hello, Java! Buffer flushed Reading: fs instanceof Readable: true fs instanceof ReadWritable: true
๐Ÿ“Œ Interface constants are public static final โ€” use them carefully
Interface constants (public static final fields) are a legacy pattern. In modern Java, prefer enum for named constant groups, or a dedicated final class with a private constructor as a constants holder. Putting too many constants in an interface leads to the "Constant Interface Antipattern" โ€” classes implementing the interface just to use constants, polluting their public API.
โญ Must-Know Interview Topics
Interface as contract ยท default modifiers (public abstract methods, public static final fields) ยท multiple implementation ยท interface vs abstract class ยท interface reference type ยท interface extending interface ยท why interface methods must be public when implemented ยท cannot instantiate interface
Q1. What is an interface in Java? How is it different from an abstract class?
An interface is a pure contract โ€” it specifies what a class must do without dictating how. All methods are implicitly public abstract (before Java 8); all fields are implicitly public static final.

Key differences from abstract class:
โ€” A class can implement multiple interfaces but extend only one class
โ€” Interfaces have no constructors and no instance fields
โ€” Interface represents a CAN-DO capability; abstract class represents an IS-A relationship
โ€” Interface methods are implicitly public; abstract class methods can have any access modifier
โ€” Use interfaces when unrelated classes need a common capability; use abstract class when related classes share state or behaviour
Q2. Can you instantiate an interface?
No โ€” directly. You cannot write new Vehicle().

However, you can create an instance in two indirect ways:
1. Implementing class: Vehicle v = new Car(); โ€” Car implements Vehicle
2. Anonymous class: Vehicle v = new Vehicle() { public void start(){...} ... }; โ€” creates a nameless class on the spot that implements the interface

Java 8 lambdas also provide a concise way to instantiate functional interfaces (interfaces with a single abstract method): Runnable r = () -> System.out.println("running");
Q3. Why must implementing methods be declared public?
Interface methods are implicitly public abstract. When you implement them in a class, you are overriding them. Java's override rule states that you cannot reduce the visibility of an overridden method. Since the interface declares the method as public, any implementation must also be public โ€” or the access would be narrowed (e.g., to package-private), which is not allowed.

Failing to add public in the implementing class results in a compile error: "cannot reduce the visibility of the inherited method."
Q4. Can an interface extend another interface? Can it extend a class?
Interface extending interface: Yes โ€” and an interface can extend multiple interfaces using the extends keyword: interface C extends A, B { }. The implementing class must implement all methods from C, A, and B.

Interface extending a class: No โ€” an interface cannot extend a class. Interfaces exist in a separate type hierarchy. They can only extend other interfaces.
Q5. What are interface variables and why are they public static final?
All fields declared in an interface are implicitly public static final:
โ€” public: accessible to all implementing classes and outside code
โ€” static: belongs to the interface type itself (no object needed); implementing classes do not each get their own copy
โ€” final: cannot be reassigned โ€” ensures constants are truly constant

This means you cannot declare instance variables in an interface, and you cannot declare a field without initialising it (blank final is not allowed in an interface). This is why interfaces cannot hold mutable state โ€” they are stateless contracts by design.
Q6. How does Java avoid the Diamond Problem with interfaces?
The Diamond Problem in C++ occurs when class D inherits from both B and C, and both inherit from A. If B and C each override A's method differently, D gets two conflicting concrete implementations โ€” ambiguity.

Java avoids this with interfaces because (before Java 8) interfaces had no method implementations โ€” only abstract methods. A class implementing two interfaces with the same method signature simply provides one implementation that satisfies both contracts โ€” no conflict.

With Java 8 default methods, the Diamond Problem can resurface. Java resolves it with a clear rule: if two interfaces provide conflicting default methods, the implementing class must override the method to resolve the ambiguity โ€” otherwise a compile error occurs.

๐ŸŒฑ Interfaces are the backbone of Spring Boot

Every major Spring abstraction is an interface. Your service, repository, and component code is built on top of interface types โ€” Spring injects the right concrete implementation at runtime.

โœ… Interface patterns in Spring Boot
Service layer โ€” define interface, provide one or more implementations; Spring injects the right one
Repository layer โ€” JpaRepository<T, ID> is an interface; Spring Data generates the implementation
Controller accepts interface โ€” loosely coupled; easy to swap implementations or mock in tests
@Qualifier โ€” when multiple beans implement the same interface, select which one to inject
Testability โ€” interfaces make it trivial to replace real beans with Mockito mocks
NotificationService interface โ€” Spring Boot pattern
// Contract โ€” the interface
public interface NotificationService {
    void send(String to, String message);
    String getChannel();
}

// Implementation 1
@Service
public class EmailNotificationService implements NotificationService {
    public void   send(String to, String msg) {
        System.out.printf("Email โ†’ %s: %s%n", to, msg);
    }
    public String getChannel() { return "EMAIL"; }
}

// Implementation 2
@Service
public class SmsNotificationService implements NotificationService {
    public void   send(String to, String msg) {
        System.out.printf("SMS  โ†’ %s: %s%n", to, msg);
    }
    public String getChannel() { return "SMS"; }
}

// Controller โ€” depends on interface, not concrete class
@RestController
public class AlertController {
    private final List<NotificationService> services;  // all impls injected

    public AlertController(List<NotificationService> services) {
        this.services = services;
    }

    @PostMapping("/alert")
    public void sendAll(@RequestParam String to,
                         @RequestParam String msg) {
        services.forEach(svc -> svc.send(to, msg));  // DMD per service
    }
}
๐Ÿ“Œ Spring Data JPA repository is interface magic
When you write public interface ProductRepository extends JpaRepository<Product, Long> { }, you are declaring an interface with zero method bodies. Spring Data generates a complete implementation at startup using dynamic proxies โ€” a runtime implementation of your interface. This is the ultimate example of interface-driven design: the caller (your service) depends on the interface; the implementation is created invisibly at runtime.
โญ Must-Know Exam Points
  • Interface methods โ†’ implicitly public abstract
  • Interface fields โ†’ implicitly public static final (must be initialised)
  • A class can implement multiple interfaces โ€” multiple inheritance of type
  • A class can extend one class AND implement multiple interfaces simultaneously
  • Interface cannot have constructors or instance fields
  • Implementing class must override ALL abstract methods or be declared abstract
  • Implementing method must be declared public โ€” cannot reduce visibility
  • Interface can extend multiple other interfaces
  • Interface cannot extend a class
  • You cannot instantiate an interface directly

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Access modifier of interface method by default?public abstractBoth public AND abstract โ€” not just one
Access modifier of interface field by default?public static finalAll three โ€” must be initialised at declaration
Can a class implement two interfaces with same method signature?Yes โ€” provide one implementationOne method body satisfies both contracts; no conflict
Implementing class declares method as package-private?Compile errorCannot reduce visibility from public (interface) to package-private
Can interface extend another interface?Yes โ€” even multipleinterface C extends A, B โ€” valid; uses extends not implements
Can interface extend a class?No โ€” compile errorInterfaces can only extend other interfaces
A class extends one class and implements two interfaces โ€” valid?Yesclass D extends B implements X, Y โ€” fully valid Java syntax

๐Ÿ“– One-Liner Definitions

  • Interface โ€” A pure contract in Java that defines what a class must do; all methods are implicitly public abstract, all fields are public static final.
  • implements โ€” Keyword used by a class to commit to honouring one or more interface contracts.
  • Multiple interface implementation โ€” A class can implement any number of interfaces, enabling multiple inheritance of type without the Diamond Problem.
  • Interface field โ€” Implicitly public static final; a compile-time constant shared by all implementors; cannot be instance state.
  • Interface extending interface โ€” An interface can extend one or more other interfaces using extends; the implementing class must honour all inherited contracts.
  • CAN-DO relationship โ€” The semantic meaning of implementing an interface: the class gains a capability, distinct from the IS-A relationship of class inheritance.
TOPIC 26

Interface Modern Features โ€” JDK 8 & 9

๐Ÿš€ Why Were These Features Added?

Before Java 8, adding a new abstract method to an interface broke every existing implementing class โ€” a massive backward compatibility problem. Java 8 introduced default methods and static methods inside interfaces to evolve APIs without breaking existing code. Java 9 added private methods to allow code reuse inside interfaces themselves.

INTERFACE METHOD TYPES โ€” COMPLETE PICTURE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ interface MyInterface { // 1. Abstract method (always existed) void doWork(); // must override // 2. default method โ€” Java 8 default void log() { // CAN override, has body System.out.println("default log"); } // 3. static method โ€” Java 8 static MyInterface create() { // called on interface name return () -> System.out.println("working"); } // 4. private method โ€” Java 9 private void helper() { // only called inside interface System.out.println("internal helper"); } // 5. private static method โ€” Java 9 private static void staticHelper() { // shared by static methods System.out.println("static helper"); } }

1๏ธโƒฃ default Methods (Java 8)

A default method has a complete body inside the interface. Implementing classes inherit it automatically โ€” no need to override. They can override it if they need different behaviour. This is how Java evolved the Collections API (e.g., Iterable.forEach(), Collection.stream()) without breaking millions of existing classes.

DEFAULT METHOD โ€” RULES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ interface Logger { void log(String msg); // abstract โ€” must override default void logInfo(String msg) { // default โ€” free to inherit log("[INFO] " + msg); // calls the abstract method! } default void logError(String msg) { log("[ERROR] " + msg); } } class ConsoleLogger implements Logger { public void log(String msg) { // only abstract method needed System.out.println(msg); } // logInfo and logError inherited for free โ€” no override needed // Can override logInfo() to customise format โ€” optional } Rules: โœ” Keyword: default (before return type) โœ” Always has a body โœ” Implicitly public (cannot be private/protected) โœ” Implementing class inherits it OR overrides it โœ” Can call other interface methods (including abstract ones)

โš ๏ธ Diamond Problem with default Methods

CONFLICT RESOLUTION โ€” when two interfaces provide same default method โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ interface A { default void show() { System.out.println("A"); } } interface B { default void show() { System.out.println("B"); } } class C implements A, B { // COMPILE ERROR unless we resolve the conflict: @Override public void show() { A.super.show(); // explicitly choose A's version, OR // B.super.show(); // choose B's version, OR // provide your own body entirely } } Resolution rule: class wins > specific interface > general interface 1. Class override > any default method 2. More specific interface (child) > less specific (parent) 3. If still ambiguous โ†’ must override explicitly

2๏ธโƒฃ static Methods (Java 8)

Interface static methods belong to the interface type itself โ€” they are not inherited by implementing classes or sub-interfaces. Call them with InterfaceName.methodName(). They are typically used as factory methods or utility helpers tightly tied to the interface contract.

STATIC METHOD โ€” RULES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ interface Validator { boolean validate(String input); // static factory / utility static Validator nonEmpty() { return input -> input != null && !input.isEmpty(); } static Validator maxLength(int max) { return input -> input != null && input.length() <= max; } } // Usage: call on interface name, NOT on implementing object Validator v1 = Validator.nonEmpty(); Validator v2 = Validator.maxLength(50); Rules: โœ” Must be called as InterfaceName.methodName() โœ” NOT inherited by implementing classes โœ” NOT inherited by sub-interfaces โœ” Implicitly public (or can be private in Java 9+) โœ” Common use: factory methods, utility/helper functions

3๏ธโƒฃ private & private static Methods (Java 9)

Private methods in interfaces allow code reuse between default and static methods without exposing internal helpers to implementing classes. Before Java 9, shared logic had to be duplicated across multiple default methods.

PRIVATE METHODS โ€” Java 9 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ interface Auditable { default void onCreate() { audit("CREATE"); } // default methods default void onUpdate() { audit("UPDATE"); } // share private helper default void onDelete() { audit("DELETE"); } private void audit(String action) { // private โ€” only in interface System.out.println("[AUDIT] " + action + " at " + System.currentTimeMillis()); } static Auditable noOp() { return new Auditable() {}; } private static String timestamp() { // private static helper return String.valueOf(System.currentTimeMillis()); } } Rules: โœ” private โ€” can only be called by other methods inside the interface โœ” NOT inherited by implementing classes โœ” NOT accessible from outside the interface at all โœ” private static โ€” shared by static methods inside the interface โœ” Eliminates code duplication between multiple default methods

๐Ÿ“Š Complete Interface Method Summary

Method TypeSinceHas Body?Inherited?Can Override?Access
abstractAlwaysโŒโœ… (as contract)โœ… (must)public
defaultJava 8โœ…โœ… (auto)โœ… (optional)public
staticJava 8โœ…โŒโŒ (hidden)public
privateJava 9โœ…โŒโŒprivate
private staticJava 9โœ…โŒโŒprivate

๐Ÿ’ป Program 1 โ€” default Methods: Logger Interface

DefaultMethodDemo.java
interface Logger {
    // Abstract โ€” must implement
    void log(String msg);

    // Default โ€” inherited for free, can override
    default void logInfo(String msg)  { log("[INFO]  " + msg); }
    default void logWarn(String msg)  { log("[WARN]  " + msg); }
    default void logError(String msg) { log("[ERROR] " + msg); }
}

// Only needs to implement log() โ€” gets the rest for free
class ConsoleLogger implements Logger {
    public void log(String msg) {
        System.out.println("Console: " + msg);
    }
}

// Overrides one default to add timestamp
class FileLogger implements Logger {
    public void log(String msg) {
        System.out.println("File: " + msg);
    }
    @Override
    public void logError(String msg) {        // custom override
        log("[ERROR][ts=" + System.currentTimeMillis() % 100000 + "] " + msg);
    }
}

public class DefaultMethodDemo {
    public static void main(String[] args) {
        Logger console = new ConsoleLogger();
        console.logInfo("App started");     // inherited default
        console.logWarn("Low memory");      // inherited default
        console.logError("Disk full");      // inherited default

        System.out.println("---");
        Logger file = new FileLogger();
        file.logInfo("Writing report");    // inherited default
        file.logError("Write failed");    // overridden with timestamp
    }
}
โ–ถ OUTPUT
Console: [INFO] App started Console: [WARN] Low memory Console: [ERROR] Disk full --- File: [INFO] Writing report File: [ERROR][ts=12345] Write failed

๐Ÿ’ป Program 2 โ€” static Methods + private Methods (Java 8 & 9)

StaticPrivateMethodDemo.java
interface MathOps {

    // Abstract
    double compute(double a, double b);

    // static factory methods (Java 8) โ€” called as MathOps.adder()
    static MathOps adder()      { return (a, b) -> a + b; }
    static MathOps multiplier() { return (a, b) -> a * b; }
    static MathOps maxOf()      { return (a, b) -> Math.max(a, b); }

    // default methods calling private helper (Java 9)
    default double computeAndLog(double a, double b) {
        double result = compute(a, b);
        printResult(a, b, result);        // calls private method
        return result;
    }
    default double computeDoubled(double a, double b) {
        double result = compute(a, b) * 2;
        printResult(a, b, result);        // same private helper โ€” no duplication
        return result;
    }

    // private method โ€” Java 9, NOT visible to implementing classes
    private void printResult(double a, double b, double r) {
        System.out.printf("  compute(%.1f, %.1f) = %.1f%n", a, b, r);
    }
}

public class StaticPrivateMethodDemo {
    public static void main(String[] args) {
        // static factory โ€” called on interface name, not object
        MathOps add  = MathOps.adder();
        MathOps mul  = MathOps.multiplier();
        MathOps maxF = MathOps.maxOf();

        System.out.println("=== add.computeAndLog ===");
        add.computeAndLog(3, 4);

        System.out.println("=== mul.computeDoubled ===");
        mul.computeDoubled(3, 4);

        System.out.println("=== max of 7, 2 ===");
        maxF.computeAndLog(7, 2);
    }
}
โ–ถ OUTPUT
=== add.computeAndLog === compute(3.0, 4.0) = 7.0 === mul.computeDoubled === compute(3.0, 4.0) = 24.0 === max of 7, 2 === compute(7.0, 2.0) = 7.0

๐Ÿ’ป Program 3 โ€” Diamond Problem Resolution

DiamondResolution.java
interface A {
    default void greet() { System.out.println("Hello from A"); }
}
interface B extends A {
    default void greet() { System.out.println("Hello from B"); }
}
interface C extends A {
    default void greet() { System.out.println("Hello from C"); }
}

// Case 1: B is more specific than A โ†’ B wins automatically
class D implements A, B {
    // No override needed โ€” B.greet() wins (B extends A โ†’ more specific)
}

// Case 2: B and C both extend A โ€” true ambiguity โ†’ MUST override
class E implements B, C {
    @Override
    public void greet() {
        B.super.greet();          // explicitly delegate to B
        System.out.println("(resolved in E)");
    }
}

public class DiamondResolution {
    public static void main(String[] args) {
        new D().greet();    // B wins (more specific)
        new E().greet();    // explicitly resolved in E
    }
}
โ–ถ OUTPUT
Hello from B Hello from B (resolved in E)
๐Ÿ’ก JDK itself uses default methods heavily
Java 8 added default methods to existing interfaces without breaking any existing code:
Iterable.forEach(), Iterable.spliterator()
Collection.stream(), Collection.removeIf()
Map.getOrDefault(), Map.computeIfAbsent(), Map.merge()
Comparator.reversed(), Comparator.thenComparing()
Every Java application uses these daily without knowing the API evolution magic behind them.
โญ Must-Know Interview Topics
Why default methods were added ยท default method inheritance vs override ยท static method not inherited ยท private method purpose (Java 9) ยท Diamond Problem with default methods and resolution rules ยท InterfaceName.super.method() syntax ยท JDK examples of default methods
Q1. Why were default methods introduced in Java 8?
Backward compatibility. Before Java 8, any new method added to an interface immediately broke every existing implementing class โ€” they all had to provide a body for the new method or fail to compile.

Java 8 wanted to add powerful new methods to existing interfaces like Collection, Iterable, and Map for the Streams API. Adding these as abstract methods would have broken every library and application that implemented those interfaces.

Default methods let the JDK provide a default implementation. Existing classes inherit it automatically. Classes that want different behaviour can still override it. This is how Collection.stream(), Iterable.forEach(), and Map.computeIfAbsent() were added without breaking the Java ecosystem.
Q2. Are static methods in interfaces inherited by implementing classes?
No. Static methods in interfaces are not inherited by implementing classes or by sub-interfaces. They belong strictly to the interface type itself and must be called using the interface name: InterfaceName.staticMethod().

This is different from static methods in classes, which are inherited (though not overridden โ€” hidden instead). The decision for interface static methods was deliberate: they are utility/factory helpers scoped to the interface contract, not part of the implementing class's API.

Example: Comparator.naturalOrder() must be called as Comparator.naturalOrder(), not as MyComparator.naturalOrder().
Q3. What problem do private interface methods (Java 9) solve?
Before Java 9, if two or more default methods in an interface shared common logic, you had two bad options:
1. Duplicate the code in each default method
2. Extract to a public default method โ€” but this exposes internal logic as part of the public API, cluttering the interface contract

Java 9 private methods solve this cleanly: you write the shared logic once in a private method, and all default/static methods inside the interface call it. The private method is completely invisible to implementing classes and outside code โ€” it is a pure internal implementation detail.
Q4. How is the Diamond Problem resolved with default methods?
Java applies a three-level resolution rule when a class inherits conflicting default method implementations:

Rule 1 โ€” Class wins: If the class itself (or a superclass) provides a concrete method, it takes priority over any interface default method. A class override always wins.

Rule 2 โ€” More specific interface wins: If one interface extends the other, the more specific (child) interface's default method wins. Example: if B extends A and both define show(), and a class implements both A and B, B's version wins automatically.

Rule 3 โ€” Must override explicitly: If neither rule resolves the ambiguity (two unrelated interfaces both provide the same default method), the implementing class gets a compile error and must override the method to pick one: B.super.greet() or C.super.greet(), or provide its own body.
Q5. Can you override a default method and still call the interface's version?
Yes โ€” using the InterfaceName.super.methodName() syntax, introduced specifically for this purpose.

Example:
@Override
public void greet() {
    B.super.greet(); // calls B's default greet()
    System.out.println("extra behaviour");
}

This is similar to super.method() for class inheritance, but uses the interface name as a qualifier to disambiguate between multiple interfaces.
Q6. Can a default method be final? Can it be abstract?
final: No โ€” a default method cannot be final. That would contradict itself: final means it cannot be overridden, but default is explicitly designed to be overridable. The two modifiers are mutually exclusive.

abstract: No โ€” a default method cannot be abstract. A default method must have a body; an abstract method cannot have a body. They are mutually exclusive.

A default method can however be overridden by an implementing class and then declared final in that class โ€” but that is a feature of the class, not the interface.

๐ŸŒฑ default & static Interface Methods in Spring Boot

Spring Boot itself leverages interface default methods in several key APIs. Understanding them helps you extend Spring behaviour cleanly.

โœ… default method patterns in Spring Boot
WebMvcConfigurer โ€” all methods are default; override only what you need
HandlerInterceptor โ€” preHandle/postHandle/afterCompletion all have empty default implementations
UserDetailsService โ€” your implementation provides loadUserByUsername(); other helpers via default methods
HealthIndicator โ€” default implementation calls doHealthCheck(); you override just the check logic
Comparator (JDK) โ€” reversed(), thenComparing() are default methods used heavily in Spring batch sorting
WebMvcConfigurer โ€” only override what you need (default methods)
// WebMvcConfigurer has ~15 methods โ€” ALL are default with empty bodies.
// You only override the ones relevant to your configuration.

@Configuration
public class WebConfig implements WebMvcConfigurer {

    // Override only CORS config โ€” all other defaults inherited
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("https://myfrontend.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE");
    }

    // Override only interceptor config
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoggingInterceptor())
                .addPathPatterns("/api/**");
    }
    // All other 13 WebMvcConfigurer methods inherited as no-ops
    // This is the power of default methods โ€” clean, minimal overrides
}

// โ”€โ”€ HandlerInterceptor with default no-op implementations โ”€โ”€โ”€โ”€โ”€
@Component
public class LoggingInterceptor implements HandlerInterceptor {

    // Only preHandle needed โ€” postHandle and afterCompletion are default no-ops
    @Override
    public boolean preHandle(HttpServletRequest  req,
                               HttpServletResponse res,
                               Object handler) {
        System.out.println("โ†’ " + req.getMethod() + " " + req.getRequestURI());
        return true;
    }
}
๐Ÿ“Œ Before Java 8: Adapter classes filled this role
Before default methods, Spring provided abstract "Adapter" classes (e.g., WebMvcConfigurerAdapter) that implemented all interface methods as empty stubs. You extended the Adapter and overrode only what you needed. Since Java 8 default methods, these Adapters were deprecated โ€” the interface itself now serves the same role. WebMvcConfigurerAdapter was removed in Spring 5.
โญ Must-Know Exam Points
  • default methods โ†’ Java 8 ยท have a body ยท implicitly public ยท inherited ยท can override
  • static methods โ†’ Java 8 ยท have a body ยท NOT inherited ยท call via InterfaceName.method()
  • private methods โ†’ Java 9 ยท have a body ยท NOT inherited ยท used inside interface only
  • private static โ†’ Java 9 ยท shared by static methods inside the interface
  • Diamond Problem rule: class > specific interface > general interface > must override
  • B.super.greet() โ€” syntax to call a specific interface's default method
  • default + final โ†’ compile error (contradictory)
  • default + abstract โ†’ compile error (contradictory)
  • Motivation: backward compatibility โ€” add methods to existing interfaces without breaking code

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Can implementing class inherit a default method?Yes โ€” automaticallydefault methods are inherited without any override needed
Can implementing class call interface static method?No โ€” must use InterfaceName.method()Static methods are NOT inherited by implementing classes
Can a private interface method be called by implementing class?No โ€” invisible outside interfaceprivate = only accessible within the interface body
Two interfaces with same default method โ€” class implements both?Compile error unless overriddenAmbiguity must be resolved explicitly by the class
Interface B extends A; both have default show(); class implements A,B?B's version wins โ€” no errorMore specific (B) wins over general (A) automatically
Can default method be declared abstract?No โ€” compile errordefault requires a body; abstract forbids a body โ€” contradictory
Which Java version added private interface methods?Java 9default and static were Java 8; private is Java 9

๐Ÿ“– One-Liner Definitions

  • default method โ€” An interface method with a body (Java 8); automatically inherited by implementing classes; overridable; solves backward compatibility when evolving APIs.
  • static interface method โ€” A method belonging to the interface type (Java 8); not inherited; called as InterfaceName.method(); used for factory/utility helpers.
  • private interface method โ€” A helper method inside an interface (Java 9); not visible to implementing classes; eliminates code duplication between default methods.
  • Diamond Problem (default methods) โ€” Ambiguity when two interfaces provide same default method; resolved by: class override > specific interface > explicit X.super.method().
  • InterfaceName.super.method() โ€” Syntax to invoke a specific interface's default method from an overriding class, used to resolve Diamond Problem ambiguity.
TOPIC 27

Enum

๐ŸŽฏ What is an Enum?

An enum (enumeration) is a special class in Java that represents a fixed set of named constants. Every constant in an enum is a public static final instance of that enum type. Enums are type-safe โ€” the compiler guarantees you can only use values from the defined set, eliminating the classic "magic number" and "magic string" bugs.

ENUM โ€” THE PROBLEM IT SOLVES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ // BAD โ€” int constants (no type safety, magic numbers) public static final int MONDAY = 1; public static final int TUESDAY = 2; void setDay(int day) { ... } // can pass 999 โ€” no compile error! // BAD โ€” String constants (typo-prone, no type safety) void setDay(String day) { ... } // can pass "MUNDAY" โ€” no compile error! // GOOD โ€” enum (type-safe, IDE-assisted, self-documenting) enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY } void setDay(Day day) { ... } // only Day values allowed โ€” compile-safe!

๐Ÿ—๏ธ Enum Internals โ€” What the Compiler Generates

An enum is syntactic sugar. The compiler converts it into a class that extends java.lang.Enum. Each constant becomes a public static final instance of the enum class, created exactly once when the class loads.

WHAT THE COMPILER GENERATES FOR enum Season { SPRING, SUMMER } โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ final class Season extends java.lang.Enum<Season> { public static final Season SPRING = new Season("SPRING", 0); public static final Season SUMMER = new Season("SUMMER", 1); private Season(String name, int ordinal) { super(name, ordinal); // Enum() stores name + ordinal } public static Season[] values() { ... } public static Season valueOf(String s) { ... } // + name(), ordinal(), toString(), equals(), hashCode(), compareTo() } Key facts: โ†’ extends java.lang.Enum (implicit, cannot extend anything else) โ†’ final (cannot be subclassed by user code) โ†’ constructor is private (only compiler creates constants) โ†’ Singletons by design โ€” each constant is exactly one object

๐Ÿ”ง Built-in Enum Methods

BUILT-IN METHODS EVERY ENUM HAS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Day d = Day.WEDNESDAY; d.name() โ†’ "WEDNESDAY" (exact declaration name as String) d.ordinal() โ†’ 2 (0-based position in declaration order) d.toString() โ†’ "WEDNESDAY" (same as name() by default) Day.values() โ†’ Day[] (array of ALL constants in order) Day.valueOf("FRIDAY") โ†’ Day.FRIDAY (parse String โ†’ enum constant) d.compareTo(Day.MONDAY) โ†’ positive (ordinal-based comparison) Enum.values() creates a NEW array each time โ€” do not call in loops! Cache it: private static final Day[] DAYS = Day.values();

โš™๏ธ Enum with Fields, Constructor & Methods

Enums can have instance fields, a private constructor, and methods โ€” making them far more powerful than simple named constants. Each constant passes arguments to the constructor at declaration time.

ENUM WITH FIELDS AND CONSTRUCTOR โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ enum Planet { MERCURY(3.303e+23, 2.4397e6), // โ† passes to constructor VENUS (4.869e+24, 6.0518e6), EARTH (5.976e+24, 6.37814e6); private final double mass; // instance field per constant private final double radius; Planet(double mass, double radius) { // always private (implicit) this.mass = mass; this.radius = radius; } double surfaceGravity() { final double G = 6.67300E-11; return G * mass / (radius * radius); } double surfaceWeight(double otherMass) { return otherMass * surfaceGravity(); } } Planet.EARTH.surfaceGravity() โ†’ ~9.8 Planet.MARS.surfaceWeight(75.0) โ†’ weight on Mars

๐Ÿ”€ Enum in switch Statements

ENUM IN switch โ€” CLASSIC AND MODERN โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Day today = Day.SATURDAY; // Classic switch switch (today) { case SATURDAY: case SUNDAY: System.out.println("Weekend!"); break; default: System.out.println("Weekday"); } // Modern switch expression (Java 14+) String type = switch (today) { case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday"; case SATURDAY, SUNDAY -> "Weekend"; }; Note: in switch cases, write just SATURDAY (not Day.SATURDAY) โ€” the compiler already knows the type from the switch expression.

๐Ÿ”Œ Enum Implementing an Interface

Enums can implement interfaces. Each constant can even override the interface method differently using constant-specific bodies.

ENUM IMPLEMENTING INTERFACE + ABSTRACT METHOD PER CONSTANT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ interface Operable { double apply(double a, double b); } enum Operation implements Operable { ADD { public double apply(double a, double b) { return a + b; } }, SUB { public double apply(double a, double b) { return a - b; } }, MUL { public double apply(double a, double b) { return a * b; } }, DIV { public double apply(double a, double b) { return a / b; } }; } Operation.ADD.apply(3, 4) โ†’ 7.0 Operation.DIV.apply(10, 2) โ†’ 5.0
๐Ÿ’ก Enum is the best Singleton (Joshua Bloch โ€” Effective Java)
A single-constant enum is the most robust Singleton in Java: thread-safe by default, serialisation-safe (JVM guarantees only one instance even after deserialization), and immune to reflection attacks (cannot create new instances via reflection). See Topic 11 for Singleton patterns โ€” enum AppConfig { INSTANCE; } beats all other approaches.

๐Ÿ’ป Program 1 โ€” Enum Basics: Day with switch

EnumBasics.java
enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

public class EnumBasics {
    public static void main(String[] args) {

        // Basic usage
        Day today = Day.WEDNESDAY;
        System.out.println("Today:   "   + today);
        System.out.println("Name:    "   + today.name());
        System.out.println("Ordinal: "   + today.ordinal());
        System.out.println("valueOf: "   + Day.valueOf("FRIDAY"));

        // Iterate all constants
        System.out.print("All days: ");
        for (Day d : Day.values())
            System.out.print(d.ordinal() + ":" + d + " ");
        System.out.println();

        // switch with enum
        for (Day d : new Day[] {Day.MONDAY, Day.SATURDAY}) {
            String type = switch (d) {
                case SATURDAY, SUNDAY -> "Weekend ๐ŸŽ‰";
                default               -> "Weekday ๐Ÿ’ผ";
            };
            System.out.println(d + " โ†’ " + type);
        }

        // enum comparison โ€” use == (singletons)
        System.out.println("today == WEDNESDAY: " + (today == Day.WEDNESDAY));
    }
}
โ–ถ OUTPUT
Today: WEDNESDAY Name: WEDNESDAY Ordinal: 2 valueOf: FRIDAY All days: 0:MONDAY 1:TUESDAY 2:WEDNESDAY 3:THURSDAY 4:FRIDAY 5:SATURDAY 6:SUNDAY MONDAY โ†’ Weekday ๐Ÿ’ผ SATURDAY โ†’ Weekend ๐ŸŽ‰ today == WEDNESDAY: true

๐Ÿ’ป Program 2 โ€” Enum with Fields, Constructor & Methods

EnumWithFields.java
enum HttpStatus {
    OK               (200, "OK"),
    CREATED          (201, "Created"),
    BAD_REQUEST      (400, "Bad Request"),
    UNAUTHORIZED     (401, "Unauthorized"),
    NOT_FOUND        (404, "Not Found"),
    INTERNAL_ERROR   (500, "Internal Server Error");

    private final int    code;
    private final String message;

    HttpStatus(int code, String message) {   // implicitly private
        this.code    = code;
        this.message = message;
    }

    public int    getCode()    { return code; }
    public String getMessage() { return message; }
    public boolean isSuccess() { return code >= 200 && code < 300; }
    public boolean isError()   { return code >= 400; }

    // Reverse lookup โ€” find enum constant by code
    public static HttpStatus fromCode(int code) {
        for (HttpStatus s : values())
            if (s.code == code) return s;
        throw new IllegalArgumentException("Unknown code: " + code);
    }

    @Override
    public String toString() {
        return code + " " + message;
    }
}

public class EnumWithFields {
    static void handleResponse(HttpStatus status) {
        System.out.printf("%-30s success=%-5b error=%b%n",
            status, status.isSuccess(), status.isError());
    }
    public static void main(String[] args) {
        for (HttpStatus s : HttpStatus.values())
            handleResponse(s);
        System.out.println("Lookup 404: " + HttpStatus.fromCode(404));
    }
}
โ–ถ OUTPUT
200 OK success=true error=false 201 Created success=true error=false 400 Bad Request success=false error=true 401 Unauthorized success=false error=true 404 Not Found success=false error=true 500 Internal Server Error success=false error=true Lookup 404: 404 Not Found

๐Ÿ’ป Program 3 โ€” EnumMap & EnumSet

EnumCollections.java โ€” EnumMap and EnumSet
import java.util.EnumMap;
import java.util.EnumSet;

enum Priority { LOW, MEDIUM, HIGH, CRITICAL }

public class EnumCollections {
    public static void main(String[] args) {

        // EnumMap โ€” faster than HashMap when keys are enum constants
        EnumMap<Priority, String> actions = new EnumMap<>(Priority.class);
        actions.put(Priority.LOW,      "Log and ignore");
        actions.put(Priority.MEDIUM,   "Email team");
        actions.put(Priority.HIGH,     "Page on-call");
        actions.put(Priority.CRITICAL, "Wake CEO + rollback");

        System.out.println("=== Incident Actions ===");
        actions.forEach((p, a) ->
            System.out.printf("%-10s โ†’ %s%n", p, a));

        // EnumSet โ€” faster than HashSet for enum values
        EnumSet<Priority> urgent =
            EnumSet.of(Priority.HIGH, Priority.CRITICAL);
        EnumSet<Priority> all    =
            EnumSet.allOf(Priority.class);
        EnumSet<Priority> nonUrgent =
            EnumSet.complementOf(urgent);    // inverse of urgent

        System.out.println("
=== Priority Sets ===");
        System.out.println("All:       " + all);
        System.out.println("Urgent:    " + urgent);
        System.out.println("Non-urgent:" + nonUrgent);
        System.out.println("HIGH urgent? " + urgent.contains(Priority.HIGH));
    }
}
โ–ถ OUTPUT
=== Incident Actions === LOW โ†’ Log and ignore MEDIUM โ†’ Email team HIGH โ†’ Page on-call CRITICAL โ†’ Wake CEO + rollback === Priority Sets === All: [LOW, MEDIUM, HIGH, CRITICAL] Urgent: [HIGH, CRITICAL] Non-urgent:[LOW, MEDIUM] HIGH urgent? true
๐Ÿ“Œ EnumMap & EnumSet โ€” always prefer over HashMap/HashSet for enum keys
EnumMap uses a simple array indexed by ordinal internally โ€” no hashing, no collision, O(1) guaranteed. It is significantly faster than HashMap<MyEnum, V>.
EnumSet uses a single long bitmask (for enums with โ‰ค64 constants) โ€” all operations are single bitwise instructions. Dramatically faster than HashSet<MyEnum>.
โญ Must-Know Interview Topics
Enum is a class extending java.lang.Enum ยท constants are public static final instances ยท constructor is private ยท cannot extend enum / cannot be subclassed ยท enum can implement interfaces ยท name() vs ordinal() vs toString() ยท valueOf() vs values() ยท enum == for comparison ยท EnumMap/EnumSet ยท Singleton via enum
Q1. What is an enum in Java? Is it a class?
Yes โ€” an enum is a special kind of class in Java. The compiler translates every enum declaration into a final class that implicitly extends java.lang.Enum<E>. Each named constant becomes a public static final field of that class, initialised exactly once when the class loads.

Because it is a class, an enum can have fields, a constructor (always private โ€” cannot be called externally), methods, and can implement interfaces. Because it is final, it cannot be subclassed by user code. Because it extends java.lang.Enum, it cannot extend any other class (Java single inheritance).
Q2. Can you create an enum object using new?
No. The enum constructor is always private (either implicitly or explicitly). Only the JVM creates enum constant instances at class loading time. You cannot call new Day() or otherwise construct additional instances.

This is what makes each enum constant a singleton โ€” there is exactly one instance of Day.MONDAY, one of Day.TUESDAY, etc., in the entire JVM. Because they are singletons, you compare enum values with == rather than equals() โ€” though equals() also works (it is final in java.lang.Enum and uses == internally).
Q3. What is the difference between name(), toString(), and ordinal()?
name(): returns the exact name as declared in the enum, as a String. It is final in java.lang.Enum โ€” you cannot override it. Always returns the declaration name.

toString(): by default returns the same as name(), but it is overridable. Override it when you want a different String representation (e.g., a display label) without changing the declaration name.

ordinal(): returns the 0-based position of the constant in its declaration order. Avoid using ordinal() for business logic โ€” it is fragile and breaks if constants are reordered. Use custom fields instead.
Q4. Can an enum extend a class? Can it implement an interface?
Extend a class: No. An enum implicitly extends java.lang.Enum, and Java allows only single class inheritance. There is no room for a second parent class.

Implement an interface: Yes. An enum can implement one or more interfaces. Each constant can even have its own body that overrides the interface method differently (constant-specific class bodies). This is a powerful pattern for providing type-safe command objects or strategy objects.
Q5. Why should you use == instead of equals() for enum comparison?
Because enum constants are singletons โ€” there is exactly one instance of each constant. == is therefore always correct, safe, and slightly faster (no method call).

equals() also works (the Enum.equals() implementation uses == internally, and it is final so cannot be overridden). However, == gives you an extra safety benefit: it is a compile-time type check. If you write day == "MONDAY" it is a compile error (comparing Day to String). With equals() it compiles but always returns false โ€” a silent bug. So == is both safer and clearer for enums.
Q6. What are EnumMap and EnumSet and why are they preferred?
EnumMap<K extends Enum, V>: a Map implementation that uses an array indexed by ordinal internally. No hashing, no buckets, no collisions. Faster and more memory-efficient than HashMap when keys are enum constants. Iteration is always in declaration order.

EnumSet<E extends Enum>: a Set implementation backed by a single long bitmask (for enums with โ‰ค 64 constants). Every operation โ€” add, remove, contains, complement โ€” is a single bitwise instruction. Dramatically faster than HashSet for enum values. Also provides convenience factory methods: allOf(), noneOf(), range(), complementOf().

Rule: whenever you use an enum as a Map key or Set element, always use EnumMap / EnumSet instead of HashMap / HashSet.

๐ŸŒฑ Enums in Spring Boot

Enums are used throughout Spring Boot for configuration, JPA column values, REST request/response mapping, and state machines.

โœ… Common enum patterns in Spring Boot
JPA @Enumerated โ€” persist enum as STRING (recommended) or ORDINAL in the DB
@RequestParam / @PathVariable โ€” Spring auto-converts String โ†’ enum in REST endpoints
@JsonProperty / @JsonValue โ€” Jackson serialises/deserialises enums to/from JSON
Order/Payment status state machine โ€” enum represents all possible states with transition methods
Spring Security roles โ€” enum Role { USER, ADMIN, MODERATOR } stored in UserDetails
OrderStatus enum + JPA Entity + REST endpoint
// Enum with @JsonValue for clean JSON output
public enum OrderStatus {
    PENDING   ("pending"),
    CONFIRMED ("confirmed"),
    SHIPPED   ("shipped"),
    DELIVERED ("delivered"),
    CANCELLED ("cancelled");

    private final String value;
    OrderStatus(String v) { this.value = v; }

    @JsonValue                            // Jackson uses this for serialisation
    public String getValue() { return value; }
}

// JPA Entity โ€” @Enumerated(STRING) recommended over ORDINAL
@Entity
public class Order {
    @Id @GeneratedValue
    private Long id;

    @Enumerated(EnumType.STRING)  // stores "PENDING" not 0 in DB
    private OrderStatus status;
}

// REST endpoint โ€” Spring auto-converts "PENDING" String โ†’ OrderStatus enum
@RestController
public class OrderController {

    @GetMapping("/orders")
    public List<Order> getByStatus(
            @RequestParam OrderStatus status) {   // "PENDING" auto-converted
        return orderRepo.findByStatus(status);
    }
}
โš ๏ธ Never use @Enumerated(ORDINAL) in production JPA
EnumType.ORDINAL stores the 0-based position (0, 1, 2โ€ฆ) in the database. If you ever reorder, insert, or remove an enum constant, all existing database rows silently map to the wrong value โ€” catastrophic data corruption with no error. Always use EnumType.STRING โ€” it stores the name as a VARCHAR, which is robust to reordering and self-documenting in the DB.
โญ Must-Know Exam Points
  • Enum is a final class that implicitly extends java.lang.Enum
  • Each constant is public static final โ€” a singleton instance
  • Constructor is always private โ€” cannot call new MyEnum()
  • Cannot extend another class (already extends java.lang.Enum)
  • Can implement one or more interfaces
  • name() โ†’ declaration name, final ยท ordinal() โ†’ 0-based position ยท toString() โ†’ overridable
  • values() โ†’ all constants as array ยท valueOf(String) โ†’ parse string to constant
  • Use == for comparison (singletons); equals() also works but == is safer
  • In switch, write case MONDAY not case Day.MONDAY
  • Enum Singleton is thread-safe, serialisation-safe, reflection-safe

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Can enum extend a class?No โ€” compile errorAlready implicitly extends java.lang.Enum; Java single inheritance
Can enum implement an interface?YesEnum is a class; classes can implement interfaces freely
Can you call new on an enum?No โ€” constructor is privateOnly JVM creates instances at class load time
First constant ordinal value?0 (zero-based)ordinal() is 0-based like arrays, not 1-based
Can enum constructor be public?No โ€” must be private or package-private (effectively private)Compiler forces private; public constructor is a compile error
Difference: name() vs toString()?name() is final; toString() is overridableBoth return declaration name by default, but only toString() can be overridden
Enum in switch โ€” correct case syntax?case MONDAY (no enum type prefix)The type is known from switch expression; prefixing causes compile error

๐Ÿ“– One-Liner Definitions

  • enum โ€” A special final class in Java representing a fixed set of named constants; each constant is a public static final singleton instance of the enum type.
  • ordinal() โ€” Returns the 0-based position of an enum constant in its declaration order; avoid using for business logic as it breaks on reordering.
  • name() โ€” Returns the exact declaration name of the enum constant as a String; final and cannot be overridden.
  • values() โ€” Static method returning an array of all enum constants in declaration order; creates a new array each call.
  • valueOf(String) โ€” Parses a String and returns the matching enum constant; throws IllegalArgumentException if no match.
  • EnumMap / EnumSet โ€” High-performance collections backed by array/bitmask respectively; always prefer over HashMap/HashSet when keys or elements are enum constants.
TOPIC 28

Annotations

๐Ÿ“Ž What is an Annotation?

An annotation is a metadata tag attached to code elements (classes, methods, fields, parameters, packages). It begins with @. Annotations carry information about code without changing what the code does. They can be read by the compiler, build tools, or at runtime via reflection โ€” enabling powerful frameworks like Spring Boot, JPA, and JUnit to work their magic with minimal boilerplate.

ANNOTATION โ€” THE BIG PICTURE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Annotation = a structured label you stick on code Who reads it: โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Compiler โ”‚ @Override, @Deprecated, @SuppressWarningsโ”‚ โ”‚ Build tools โ”‚ @Generated, annotation processors (APT) โ”‚ โ”‚ Runtime (JVM) โ”‚ @Autowired, @Test, @Entity, @RequestMappingโ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ They do NOT change logic โ€” they provide INSTRUCTIONS to the reader (compiler / framework / tool) about how to treat the annotated code.

๐Ÿ”ง Built-in Java Annotations

THE 5 CORE JAVA ANNOTATIONS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ @Override โ†’ Tells compiler this method overrides a parent method โ†’ Compile error if no matching method exists in supertype โ†’ Best practice: ALWAYS use when overriding @Deprecated โ†’ Marks a method/class as outdated โ†’ Compiler emits a warning when it is used โ†’ Use @Deprecated + Javadoc @deprecated with replacement info @SuppressWarnings("unchecked") // or "all", "deprecation", etc. โ†’ Tells compiler to suppress specific warning types โ†’ Does NOT fix the issue โ€” just silences the warning โ†’ Common values: "unchecked", "deprecation", "unused", "all" @FunctionalInterface โ†’ Marks interface as having exactly one abstract method (SAM) โ†’ Compiler error if interface has 0 or 2+ abstract methods โ†’ Required for lambda expressions to work cleanly @SafeVarargs โ†’ Suppresses heap pollution warnings on vararg methods โ†’ Used on final/static/private methods with generic varargs

๐Ÿ—๏ธ Meta-Annotations โ€” Annotations on Annotations

Meta-annotations configure the behaviour of custom annotations. The four main ones from java.lang.annotation:

META-ANNOTATIONS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ @Retention(RetentionPolicy.???) SOURCE โ†’ discarded after compilation (e.g. @Override) CLASS โ†’ stored in .class file but NOT available at runtime (default) RUNTIME โ†’ available at runtime via reflection (e.g. Spring annotations) @Target(ElementType.???) โ€” WHERE the annotation can be placed TYPE โ†’ class, interface, enum, record METHOD โ†’ methods FIELD โ†’ instance/static fields PARAMETER โ†’ method parameters CONSTRUCTOR โ†’ constructors LOCAL_VARIABLE โ†’ local variables ANNOTATION_TYPE โ†’ on other annotations (meta-annotation) PACKAGE โ†’ package declarations @Documented โ†’ Include annotation in Javadoc output โ†’ Without it, annotations are invisible in generated docs @Inherited โ†’ If parent class has this annotation, subclass inherits it โ†’ Only works on TYPE-targeted annotations; NOT methods/fields

โœ๏ธ Creating a Custom Annotation

CUSTOM ANNOTATION SYNTAX โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ @Retention(RetentionPolicy.RUNTIME) // available at runtime @Target(ElementType.METHOD) // only on methods @Documented public @interface LogExecutionTime { String value() default "ms"; // annotation element boolean enabled() default true; // element with default } // Usage: @LogExecutionTime // uses defaults public void processOrder() { ... } @LogExecutionTime(value = "ns", enabled = false) // custom values public void fastOp() { ... } // Reading at runtime via reflection: Method m = MyService.class.getMethod("processOrder"); LogExecutionTime ann = m.getAnnotation(LogExecutionTime.class); System.out.println(ann.value()); // "ms" Rules for annotation elements: โœ” Return type: primitives, String, Class, enum, annotation, or arrays of these โœ” Cannot throw exceptions โœ” Cannot have parameters โœ” default keyword provides fallback value

๐Ÿ”„ Annotation Processing Lifecycle

HOW ANNOTATIONS ARE PROCESSED โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. COMPILE TIME (SOURCE / CLASS retention): โ†’ @Override: compiler checks method signature against supertype โ†’ @SuppressWarnings: compiler ignores matched warning category โ†’ Lombok @Getter: APT reads annotation, generates getter methods into the .class file before compilation finishes 2. RUNTIME (RUNTIME retention): โ†’ Framework starts โ†’ scans classpath for classes / methods โ†’ Finds annotated elements via reflection: for (Method m : clazz.getMethods()) if (m.isAnnotationPresent(MyAnnotation.class)) // do something with m โ†’ Spring: @Autowired โ†’ inject dependency โ†’ JUnit: @Test โ†’ run as test method โ†’ JPA: @Entity โ†’ create DB table mapping
๐Ÿ’ก Annotations are interfaces under the hood
When you write @interface MyAnnotation, the compiler generates an interface that extends java.lang.annotation.Annotation. Each element (like String value()) becomes an abstract method in that interface. This is why annotation elements look like methods and why you read them via method calls: ann.value().

๐Ÿ’ป Program 1 โ€” Built-in Annotations in Action

BuiltInAnnotations.java
import java.util.ArrayList;
import java.util.List;

class Animal {
    void speak() { System.out.println("..."); }

    @Deprecated
    void oldSound() { System.out.println("generic sound"); }
}

class Dog extends Animal {
    @Override                       // compile error if speak() not in Animal
    void speak() {
        System.out.println("Woof!");
    }

    @SuppressWarnings("deprecation")  // silence the warning for this call
    void callOldMethod() {
        oldSound();
    }
}

@FunctionalInterface
interface Transformer<T, R> {
    R transform(T input);           // exactly 1 abstract method โ€” SAM
    // void extra();               // โ† would cause compile error!
}

public class BuiltInAnnotations {
    @SuppressWarnings("unchecked")    // suppress raw type warning
    static List rawList() {
        return new ArrayList();         // raw type โ€” would warn without annotation
    }

    public static void main(String[] args) {
        new Dog().speak();

        // Transformer as lambda (works because @FunctionalInterface)
        Transformer<String, Integer> len = s -> s.length();
        System.out.println("Length of "hello": " + len.transform("hello"));

        new Dog().callOldMethod();    // no compiler warning
    }
}
โ–ถ OUTPUT
Woof! Length of "hello": 5 generic sound

๐Ÿ’ป Program 2 โ€” Custom Annotation + Runtime Reflection

CustomAnnotation.java โ€” define, apply, read at runtime
import java.lang.annotation.*;
import java.lang.reflect.Method;

// โ”€โ”€ Define custom annotation โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@Retention(RetentionPolicy.RUNTIME)   // survives to runtime
@Target(ElementType.METHOD)           // only on methods
@Documented
@interface Audit {
    String  action()  default "CALL";
    boolean logArgs() default false;
}

// โ”€โ”€ Apply annotation โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class OrderService {

    @Audit(action = "CREATE_ORDER", logArgs = true)
    public void createOrder(String item, int qty) {
        System.out.println("Creating order: " + item + " x" + qty);
    }

    @Audit                           // uses defaults: action="CALL", logArgs=false
    public void listOrders() {
        System.out.println("Listing all orders");
    }

    public void helperMethod() {      // no annotation
        System.out.println("internal helper");
    }
}

// โ”€โ”€ Read annotation at runtime via reflection โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
public class CustomAnnotation {
    public static void main(String[] args) {
        System.out.println("=== Scanning @Audit annotations ===");
        for (Method m : OrderService.class.getDeclaredMethods()) {
            if (m.isAnnotationPresent(Audit.class)) {
                Audit a = m.getAnnotation(Audit.class);
                System.out.printf("Method: %-14s | action=%-14s | logArgs=%b%n",
                    m.getName(), a.action(), a.logArgs());
            }
        }
        System.out.println("=== Invoking service ===");
        OrderService svc = new OrderService();
        svc.createOrder("Book", 3);
        svc.listOrders();
    }
}
โ–ถ OUTPUT
=== Scanning @Audit annotations === Method: createOrder | action=CREATE_ORDER | logArgs=true Method: listOrders | action=CALL | logArgs=false === Invoking service === Creating order: Book x3 Listing all orders

๐Ÿ’ป Program 3 โ€” Repeatable Annotation (Java 8)

RepeatableAnnotation.java โ€” @Repeatable meta-annotation
import java.lang.annotation.*;
import java.lang.reflect.Method;

// Container annotation (holds array of @Role)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Roles {
    Role[] value();
}

// Repeatable annotation
@Repeatable(Roles.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Role {
    String value();
}

class AdminController {
    @Role("ADMIN")
    @Role("SUPERUSER")                     // multiple @Role on same method
    public void deleteAll() {
        System.out.println("Deleting all records");
    }
    @Role("USER")
    public void viewDashboard() {
        System.out.println("Viewing dashboard");
    }
}

public class RepeatableAnnotation {
    public static void main(String[] args) {
        for (Method m : AdminController.class.getDeclaredMethods()) {
            Role[] roles = m.getAnnotationsByType(Role.class);
            if (roles.length > 0) {
                System.out.print(m.getName() + " allowed for: ");
                for (Role r : roles)
                    System.out.print(r.value() + " ");
                System.out.println();
            }
        }
    }
}
โ–ถ OUTPUT
deleteAll allowed for: ADMIN SUPERUSER viewDashboard allowed for: USER
๐Ÿ“Œ Annotation elements have restricted return types
Annotation elements can only return: boolean, byte, short, int, long, float, double, char, String, Class, any enum, another annotation type, or a 1D array of any of the above. You cannot use Object, List, Map, or other reference types โ€” this is enforced at compile time.
โญ Must-Know Interview Topics
Annotation = metadata ยท @Override / @Deprecated / @SuppressWarnings / @FunctionalInterface ยท meta-annotations (@Retention, @Target, @Inherited, @Documented) ยท RetentionPolicy SOURCE/CLASS/RUNTIME ยท custom annotation syntax (@interface) ยท reading at runtime via reflection ยท annotation elements restricted types ยท @Repeatable
Q1. What is an annotation in Java? Does it change program logic?
An annotation is structured metadata attached to a code element using the @ prefix. It provides information about the code to the compiler, build tools, or runtime framework โ€” without directly altering what the code does.

Annotations themselves do not change program logic. What changes behaviour is the processor that reads the annotation and acts on it: the compiler (for @Override), a build-time processor like Lombok (for @Getter), or a runtime framework like Spring (for @Autowired, @Transactional). The annotation is the instruction; the framework is the executor.
Q2. What are the three RetentionPolicy values and what do they mean?
SOURCE: The annotation exists only in the source file. It is discarded by the compiler and not present in the .class file. Used for annotations that are purely for developer tools or IDEs: @Override, @SuppressWarnings.

CLASS: The annotation is stored in the .class file but is not loaded into the JVM at runtime. Not accessible via reflection. This is the default if @Retention is not specified. Used by bytecode analysis tools.

RUNTIME: The annotation survives compilation, is stored in the .class file, and is loaded into the JVM. Accessible at runtime via reflection. Required for all framework annotations: Spring, JPA, JUnit, Jackson, etc.
Q3. What is @Override and why should you always use it?
@Override tells the compiler that the annotated method is intended to override a method in a supertype (class or interface). The compiler verifies the claim: if no matching method exists in the supertype, it produces a compile error.

Why always use it:
1. Catches typos: if you write tostring() instead of toString() and add @Override, you get a compile error instead of a silent bug where your method is never called.
2. Documents intent: clearly signals this method participates in polymorphism.
3. Refactoring safety: if the parent method is renamed or removed, all @Override sites immediately fail to compile โ€” no silent orphaned methods.
Q4. What is @FunctionalInterface and what does it guarantee?
@FunctionalInterface marks an interface as a Functional Interface โ€” one that has exactly one abstract method (SAM: Single Abstract Method). This is required for the interface to be used as a lambda expression target.

The annotation does two things:
1. Documents intent: signals to readers that this interface is designed for lambdas.
2. Enforces the contract: if a second abstract method is added to the interface, the compiler immediately reports an error โ€” preventing accidental breakage of all lambda usage sites.

Default and static methods do not count toward the SAM limit. Examples: Runnable, Comparator, Predicate, Function, Consumer, Supplier are all functional interfaces.
Q5. What is the difference between @Inherited and regular annotation?
By default, annotations are not inherited โ€” if a parent class has an annotation, the subclass does not automatically have it.

Adding the @Inherited meta-annotation to a custom annotation changes this: if the annotation is placed on a class, any subclass of that class is treated as if it also has the annotation (when queried via getAnnotation()).

Important limitations of @Inherited:
โ€” Works only for class-level annotations (@Target(ElementType.TYPE))
โ€” Does NOT work for interfaces (annotating an interface does not make implementing classes inherit it)
โ€” Does NOT work for method-level or field-level annotations
Q6. How do you read annotation values at runtime?
Via the Java Reflection API โ€” but only if the annotation has @Retention(RetentionPolicy.RUNTIME):

1. Get the reflective element: Method m = MyClass.class.getMethod("methodName");
2. Check presence: m.isAnnotationPresent(MyAnnotation.class)
3. Retrieve the instance: MyAnnotation ann = m.getAnnotation(MyAnnotation.class);
4. Read element values: ann.value(), ann.enabled(), etc.

The same API works for classes (MyClass.class.getAnnotation(...)), fields (Field.getAnnotation(...)), and parameters (Parameter.getAnnotation(...)). This is precisely what Spring does at startup when scanning your beans for @Autowired, @Transactional, etc.

๐ŸŒฑ Annotations Power All of Spring Boot

Spring Boot is almost entirely annotation-driven. Understanding which annotations belong to which layer โ€” and what each one tells the framework โ€” is essential for every Spring developer.

โœ… Essential Spring Boot annotations by layer
Component scanning: @Component, @Service, @Repository, @Controller, @RestController โ€” register beans
Dependency injection: @Autowired, @Qualifier, @Primary, @Value
Web layer: @RequestMapping, @GetMapping, @PostMapping, @PathVariable, @RequestBody, @ResponseBody
Data layer: @Entity, @Table, @Id, @Column, @OneToMany, @Transactional
Configuration: @Configuration, @Bean, @Profile, @ConditionalOnProperty
AOP / cross-cutting: @Aspect, @Before, @Around, @AfterReturning
Custom @LogExecutionTime annotation + AOP aspect in Spring Boot
// โ”€โ”€ Custom annotation โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecutionTime {}

// โ”€โ”€ Service using the annotation โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@Service
public class ReportService {

    @LogExecutionTime            // mark this method for timing
    public void generateReport() {
        // simulate work
        try { Thread.sleep(50); }
        catch (InterruptedException e) { Thread.currentThread().interrupt(); }
    }
}

// โ”€โ”€ AOP aspect reads the annotation at runtime โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@Aspect @Component
public class TimingAspect {

    @Around("@annotation(LogExecutionTime)")  // pointcut = our annotation
    public Object timeMethod(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();        // run the actual method
        long elapsed = System.currentTimeMillis() - start;
        System.out.printf("%s took %d ms%n",
            pjp.getSignature().getName(), elapsed);
        return result;
    }
}
๐Ÿ“Œ This is how Spring AOP, @Transactional, and @Cacheable work internally
Spring wraps your bean in a proxy at startup. When a method annotated with @Transactional is called, the proxy intercepts it, starts a transaction, runs your method, then commits or rolls back. @Cacheable works the same way โ€” the proxy checks the cache before calling your method. All of this is powered by reflection reading annotations at runtime โ€” exactly the same mechanism as the custom @LogExecutionTime example above.
โญ Must-Know Exam Points
  • Annotation = metadata; defined with @interface
  • Does NOT change logic โ€” processor reads & acts on it
  • @Override โ†’ SOURCE retention; compile-time check only
  • @Deprecated โ†’ compiler warning when used
  • @SuppressWarnings โ†’ silences specific compiler warnings
  • @FunctionalInterface โ†’ enforces exactly 1 abstract method (SAM)
  • @Retention: SOURCE โ†’ class file only โ†’ RUNTIME (needed for reflection)
  • @Target: restricts where annotation can be placed
  • @Inherited: subclass inherits class-level annotation (not interface)
  • Custom annotation elements: only primitives, String, Class, enum, annotation, 1D arrays
  • Read at runtime: isAnnotationPresent(), getAnnotation()

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Default RetentionPolicy if @Retention not specified?CLASSNot SOURCE, not RUNTIME โ€” CLASS is the default
Can annotation element return List<String>?No โ€” compile errorOnly primitives, String, Class, enum, annotation, 1D arrays allowed
@Override retention policy?SOURCE โ€” discarded after compilationCompiler uses it; JVM never sees it
Can @FunctionalInterface have default methods?Yes โ€” unlimitedOnly abstract methods count toward the SAM limit; default/static are fine
Does @Inherited work on interfaces?No โ€” only class inheritanceImplementing a @Inherited-annotated interface does NOT give the class the annotation
Can you place two @SuppressWarnings on one element?Yes โ€” use array: @SuppressWarnings({"unchecked","deprecation"})Not @Repeatable; use array syntax for multiple values
What keyword defines a custom annotation?@interfaceNot interface, not annotation โ€” specifically @interface

๐Ÿ“– One-Liner Definitions

  • Annotation โ€” Metadata attached to code elements using @; provides instructions to compiler, build tools, or runtime frameworks without altering logic.
  • @interface โ€” Keyword used to declare a custom annotation type; the compiler generates an interface extending java.lang.annotation.Annotation.
  • @Retention โ€” Meta-annotation specifying how long an annotation survives: SOURCE (compile only), CLASS (in .class file), or RUNTIME (available via reflection).
  • @Target โ€” Meta-annotation restricting which code elements an annotation can be applied to (TYPE, METHOD, FIELD, PARAMETER, etc.).
  • @FunctionalInterface โ€” Marks an interface as having exactly one abstract method; enables lambda expression usage and enforces SAM at compile time.
  • RetentionPolicy.RUNTIME โ€” Required for any annotation that needs to be read at runtime by frameworks like Spring, JPA, or JUnit via reflection.
TOPIC 29

Var-Arg Methods

๐Ÿ“ฆ What are Var-Args?

Variable-arity arguments (var-args) allow a method to accept zero or more arguments of a specified type without the caller needing to create an array manually. The syntax is Type... paramName. Inside the method, the parameter is treated as an array. Var-args were introduced in Java 5 to eliminate the need for overloaded versions of methods like printf().

VAR-ARG SYNTAX AND BASIC RULES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ // Declaration void method(Type... paramName) // Internally treated as: void method(Type[] paramName) // Calling โ€” all of these are valid: method(); // zero args โ†’ array length 0 method(a); // one arg โ†’ array length 1 method(a, b, c); // three args โ†’ array length 3 method(new Type[]{a, b}); // explicit array also works // Example: int sum(int... nums) { int total = 0; for (int n : nums) total += n; return total; } sum() โ†’ 0 sum(1) โ†’ 1 sum(1, 2, 3) โ†’ 6 sum(1,2,3,4,5) โ†’ 15

๐Ÿ“‹ Var-Arg Rules

ALL RULES FOR VAR-ARGS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Rule 1 โ€” MUST be the LAST parameter: void log(String level, String... messages) โœ… void log(String... messages, String level) โŒ compile error Rule 2 โ€” Only ONE var-arg per method: void bad(int... a, String... b) โŒ compile error Rule 3 โ€” Can have zero or more regular params before it: void print(String... msgs) โœ… only var-arg void print(int code, String... msgs) โœ… regular + var-arg void print(int a, int b, String... msgs) โœ… multiple regular + var-arg Rule 4 โ€” Treated as an array inside the method: void show(String... items) { System.out.println(items.length); // array property for (String s : items) { ... } // array iteration items[0] // array indexing } Rule 5 โ€” Passing explicit array is also valid: String[] arr = {"a", "b", "c"}; show(arr); // โœ… works โ€” compiler treats array as the vararg

๐Ÿ”„ How the Compiler Handles Var-Args

WHAT THE COMPILER DOES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Source code: void greet(String... names) { ... } greet("Alice", "Bob", "Charlie"); Compiler transforms call to: greet(new String[] { "Alice", "Bob", "Charlie" }); The method signature in bytecode: void greet(String[]) โ† identical to a plain array parameter This is why you can pass a String[] directly: String[] team = {"Alice", "Bob"}; greet(team); โœ… โ€” array IS the vararg And why vararg and array overloads CANNOT coexist: void greet(String... names) { } // compiles void greet(String[] names) { } // โŒ duplicate method โ€” same erasure!

โš ๏ธ Var-Arg Overloading โ€” Ambiguity Traps

OVERLOAD RESOLUTION WITH VAR-ARGS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ void show(int x) { System.out.println("int"); } void show(int... nums) { System.out.println("vararg"); } show(5); โ†’ "int" โ† fixed param wins over vararg (exact match preferred) show(5, 6); โ†’ "vararg" โ† only vararg can take two ints show(); โ†’ "vararg" โ† only vararg accepts zero args void show(Object... o) { ... } void show(String... s) { ... } show("hello"); โ†’ "String..." (more specific wins) show("a","b"); โ†’ ambiguous in some contexts โ€” avoid! General rule: fixed-arity methods always win over var-arg methods when both match the call. Var-arg is the last resort.

๐Ÿ”— Var-Arg with Generics โ€” Heap Pollution Warning

@SafeVarargs โ€” suppress heap pollution warning โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ // Generic vararg causes compiler warning: // "Possible heap pollution from parameterized vararg type" @SafeVarargs // suppresses the warning @SuppressWarnings("varargs") static <T> List<T> listOf(T... items) { return Arrays.asList(items); } List<String> names = listOf("Alice", "Bob", "Charlie"); Why the warning? Because T... internally creates an Object[] when T is a generic type, which can lead to ClassCastException if misused (heap pollution). @SafeVarargs says "I guarantee I don't do anything unsafe with the vararg array." Only applicable on final/static/private methods.
๐Ÿ’ก JDK var-arg methods you use every day
System.out.printf(String format, Object... args)
String.format(String format, Object... args)
Arrays.asList(T... a)
List.of(E... elements)
Collections.addAll(Collection c, T... elements)
Objects.hash(Object... values)
All of these are var-arg methods that make the caller's code clean without requiring explicit array construction.

๐Ÿ’ป Program 1 โ€” Var-Arg Basics: sum, print, log

VarArgBasics.java
public class VarArgBasics {

    // Plain vararg โ€” zero or more ints
    static int sum(int... nums) {
        int total = 0;
        for (int n : nums) total += n;
        return total;
    }

    // Regular param + vararg โ€” level prefix before variable messages
    static void log(String level, String... messages) {
        for (String msg : messages)
            System.out.println("[" + level + "] " + msg);
    }

    // Vararg with array access and length
    static double average(double... vals) {
        if (vals.length == 0) return 0;
        double sum = 0;
        for (double v : vals) sum += v;
        return sum / vals.length;
    }

    public static void main(String[] args) {
        // sum โ€” zero to many args
        System.out.println("sum()        = " + sum());
        System.out.println("sum(5)       = " + sum(5));
        System.out.println("sum(1,2,3)   = " + sum(1, 2, 3));
        System.out.println("sum(array)   = " + sum(new int[]{10, 20, 30}));

        // log โ€” fixed param + vararg
        log("INFO", "App started");
        log("WARN", "Low disk", "Low memory");
        log("DEBUG");               // zero messages โ€” OK

        // average
        System.out.printf("avg(1,2,3,4,5) = %.1f%n", average(1,2,3,4,5));
        System.out.printf("avg()          = %.1f%n", average());
    }
}
โ–ถ OUTPUT
sum() = 0 sum(5) = 5 sum(1,2,3) = 6 sum(array) = 60 [INFO] App started [WARN] Low disk [WARN] Low memory avg(1,2,3,4,5) = 3.0 avg() = 0.0

๐Ÿ’ป Program 2 โ€” Overloading with Var-Args

VarArgOverloading.java โ€” resolution rules in practice
public class VarArgOverloading {

    static void display(int x) {
        System.out.println("fixed int: " + x);
    }
    static void display(int... nums) {
        System.out.println("vararg int[]: len=" + nums.length);
    }
    static void display(String s, Object... extras) {
        System.out.print("String+"+extras.length+" extras: " + s);
        for (Object o : extras) System.out.print(" | " + o);
        System.out.println();
    }

    public static void main(String[] args) {
        display(42);          // fixed int wins (exact match preferred)
        display(1, 2, 3);    // vararg (only option for 3 ints)
        display();            // vararg (only option for 0 args)
        display("Hello");     // String + 0 extras
        display("Hello", 1, true, "extra");  // String + 3 extras

        // Passing explicit array as vararg
        int[] data = {10, 20, 30};
        display(data);         // vararg accepts int[] directly
    }
}
โ–ถ OUTPUT
fixed int: 42 vararg int[]: len=3 vararg int[]: len=0 String+0 extras: Hello String+3 extras: Hello | 1 | true | extra vararg int[]: len=3

๐Ÿ’ป Program 3 โ€” Practical Var-Arg: Builder-style tag formatter

VarArgPractical.java โ€” real-world SQL IN clause + String join
import java.util.Arrays;
import java.util.stream.Collectors;

public class VarArgPractical {

    // Build SQL IN clause from variable number of IDs
    static String buildInClause(String column, Object... values) {
        if (values.length == 0)
            return column + " IN ()";
        String placeholders = Arrays.stream(values)
            .map(v -> "'" + v + "'")
            .collect(Collectors.joining(", "));
        return column + " IN (" + placeholders + ")";
    }

    // Validate multiple non-null fields at once
    static void requireNonNull(String context, Object... fields) {
        for (int i = 0; i < fields.length; i++)
            if (fields[i] == null)
                throw new IllegalArgumentException(
                    context + ": field[" + i + "] is null");
        System.out.println(context + ": all " + fields.length + " fields valid");
    }

    // Concatenate strings with separator
    static String joinWith(String sep, String... parts) {
        return String.join(sep, parts);
    }

    public static void main(String[] args) {
        // SQL IN clause
        System.out.println(buildInClause("status", "ACTIVE", "PENDING"));
        System.out.println(buildInClause("id", 1, 2, 3, 4));
        System.out.println(buildInClause("tag"));

        // Null validation
        requireNonNull("createUser", "Alice", "alice@ex.com", "password");
        try {
            requireNonNull("createUser", "Bob", null, "password");
        } catch (IllegalArgumentException e) {
            System.out.println("Caught: " + e.getMessage());
        }

        // joinWith
        System.out.println(joinWith(", ",  "Java", "Spring", "Hibernate"));
        System.out.println(joinWith(" / ", "home", "user", "docs"));
    }
}
โ–ถ OUTPUT
status IN ('ACTIVE', 'PENDING') id IN ('1', '2', '3', '4') tag IN () createUser: all 3 fields valid Caught: createUser: field[1] is null Java, Spring, Hibernate home / user / docs
โš ๏ธ null with var-arg โ€” two very different things
method((String) null) โ€” passes a single null element; args.length == 1, args[0] == null
method((String[]) null) โ€” passes null as the entire array; calling args.length throws NullPointerException
Always null-check args itself if callers might pass an explicit null array, or use method((Type) null) intentionally to pass a null element.
โญ Must-Know Interview Topics
Var-arg syntax (Type...) ยท treated as array internally ยท must be last parameter ยท only one per method ยท passing explicit array ยท overload resolution (fixed wins over vararg) ยท vararg vs array overload cannot coexist ยท null trap ยท @SafeVarargs for generics ยท JDK examples
Q1. What is a var-arg method? How is it different from passing an array?
A var-arg method accepts zero or more arguments of a specified type using the Type... syntax. The compiler translates multiple arguments at the call site into an array automatically โ€” the caller doesn't need to construct one.

The difference is purely at the call site:
โ€” With an array parameter, the caller must create an array: method(new int[]{1,2,3})
โ€” With var-arg, the caller simply passes values: method(1, 2, 3)
โ€” Both look identical inside the method body (it's an array either way)

You can also pass an explicit array to a var-arg method โ€” both forms compile. However, you cannot have both a var-arg and an array overload for the same method signature โ€” they produce duplicate bytecode.
Q2. What are the rules for using var-args?
1. Must be the last parameter: void m(int a, String... s) โœ… โ€” void m(String... s, int a) โŒ
2. Only one var-arg per method: void m(int... a, String... b) โŒ
3. Can have zero regular parameters before it: void m(String... s) โœ…
4. Inside the method, it is an array โ€” use .length, indexing, for-each
5. Passing an explicit array is valid: m(new String[]{"a","b"}) โœ…
6. Var-arg and array of same type cannot both be overloads โ€” same bytecode signature
Q3. When a fixed-arity and a var-arg method both match a call, which wins?
The fixed-arity method always wins when it exactly matches the call.

Java's method overload resolution has three phases:
1. First, try to find a match without boxing/unboxing or var-arg expansion
2. Then, try with boxing/unboxing but no var-arg
3. Finally, try with var-arg expansion

Var-arg is only considered in phase 3 โ€” the last resort. So display(5) calls display(int) over display(int...) because the fixed version is found in phase 1, before var-arg even enters consideration.
Q4. Can you overload a method with both Type[] and Type...?
No โ€” compile error: duplicate method.

The compiler converts Type... param into Type[] param in the bytecode. So void show(String... s) and void show(String[] s) produce exactly the same method descriptor in the .class file. The compiler sees this as a duplicate and rejects it.

This also means a var-arg method can always be called with an explicit array โ€” there is no ambiguity because they are, under the hood, the same thing.
Q5. What happens when you pass null to a var-arg method?
There are two distinct cases:

Case 1: method((String) null) โ€” cast to the element type. The compiler creates a single-element array containing null. Inside the method, args.length == 1 and args[0] == null. Safe โ€” no NPE unless you dereference args[0].

Case 2: method((String[]) null) โ€” cast to the array type. The compiler passes null as the entire vararg array. Inside the method, args itself is null. Calling args.length immediately throws NullPointerException.

Without an explicit cast, just passing null is ambiguous and causes a compiler warning. Always cast null explicitly when calling var-arg methods.
Q6. What is @SafeVarargs and when is it needed?
When a method has a generic var-arg parameter (T... items), the compiler warns about potential heap pollution โ€” because T... is internally stored as Object[], and mixing generic types in that array can lead to ClassCastException at unexpected places.

@SafeVarargs suppresses this warning. It is the developer's declaration that the method does not perform any unsafe operations on the vararg array (e.g., it only reads from it, not writes a value of a potentially wrong type).

Restrictions: only applicable on final, static, or private methods (Java 7+) and on constructors. Not applicable on overridable methods (they could be overridden unsafely).

๐ŸŒฑ Var-Args in Spring Boot Context

Var-args appear in Spring Boot in logging, validation utilities, assertion helpers, and bean definition convenience methods.

โœ… Var-arg patterns in Spring Boot
Logging (SLF4J) โ€” log.info("{} ordered {} items", username, count) uses Object... args
MockMvc perform โ€” mockMvc.perform(get("/api").param("ids", "1","2","3"))
Spring Assert โ€” Assert.notNull(obj, "msg") and custom multi-field variants
BDDMockito / Mockito โ€” given(svc.find(anyString())).willReturn(...) uses var-arg matchers
@ConditionalOnProperty โ€” havingValue patterns with multiple property names
ValidationUtils.java โ€” var-arg validation helper in a Spring service
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

@Service
public class ProductService {

    private static final Logger log =
        LoggerFactory.getLogger(ProductService.class);

    // SLF4J uses vararg: Object... args โ€” {} placeholders
    public void createProduct(String name, double price, int stock) {
        validateFields("createProduct", name, price, stock);
        log.info("Creating product: {} price={} stock={}", name, price, stock);
        // ... persist
    }

    // Custom var-arg validation โ€” validate any number of required string fields
    private void validateFields(String method, Object... fields) {
        for (int i = 0; i < fields.length; i++) {
            if (fields[i] instanceof String s &&
                    !StringUtils.hasText(s)) {
                throw new IllegalArgumentException(
                    method + ": field["+i+"] must not be blank");
            }
            if (fields[i] instanceof Number n &&
                    n.doubleValue() < 0) {
                throw new IllegalArgumentException(
                    method + ": field["+i+"] must be non-negative");
            }
        }
    }
}
๐Ÿ“Œ SLF4J logging uses var-arg for deferred toString()
log.info("{} placed order {}", user, orderId) โ€” the Object... args var-arg means the String is only formatted if the INFO level is actually enabled. This avoids expensive toString() calls when logging is disabled โ€” a performance optimisation you get for free because the logger is var-arg-based.
โญ Must-Know Exam Points
  • Syntax: Type... paramName โ€” the ... comes after the type
  • Internally treated as Type[] โ€” all array operations available inside
  • Must be the last parameter โ€” only one var-arg per method
  • Can be called with zero to many args, or with an explicit array
  • Cannot coexist with Type[] overload โ€” same bytecode signature
  • Fixed-arity method always wins over var-arg in overload resolution
  • null as (Type) null โ†’ array with one null element
  • null as (Type[]) null โ†’ null array itself โ†’ NPE on .length
  • @SafeVarargs โ†’ suppresses heap pollution warning on generic var-args
  • JDK examples: printf(), String.format(), Arrays.asList(), List.of()

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
void m(int... a, String b) โ€” valid?No โ€” compile errorVar-arg must be LAST parameter; String b cannot come after
void m(int... a, double... b) โ€” valid?No โ€” compile errorOnly ONE var-arg allowed per method
void m(int... a) and void m(int[] a) โ€” can both exist?No โ€” duplicate methodSame bytecode signature; compiler rejects as duplicate
m(5) when both m(int) and m(int...) exist โ€” which called?m(int) โ€” fixed winsFixed-arity wins over var-arg in overload resolution
Can var-arg accept zero arguments?Yes โ€” array length 0m() with var-arg โ†’ valid; array is empty but not null
Inside var-arg method, what type is the parameter?An array: Type[]Can use .length, indexing, for-each โ€” it IS an array
m((String[]) null) โ€” what happens inside method?NullPointerException on .lengthThe entire array reference is null; accessing length throws NPE

๐Ÿ“– One-Liner Definitions

  • Var-arg โ€” A method parameter declared as Type... that accepts zero or more arguments of that type; internally treated as an array.
  • Last-parameter rule โ€” A var-arg must always be the last parameter in a method signature; only one var-arg is allowed per method.
  • Fixed vs var-arg overload resolution โ€” When both a fixed-arity and a var-arg overload match a call, the fixed-arity method always takes priority (var-arg is last resort).
  • Var-arg and array overload conflict โ€” Type... p and Type[] p produce identical bytecode signatures; they cannot coexist as overloads.
  • @SafeVarargs โ€” Annotation suppressing heap pollution warnings on generic var-arg parameters; applicable only on final, static, or private methods.
TOPIC 30

Functional Interface & Lambda Expressions

๐ŸŽฏ What is a Functional Interface?

A functional interface is any interface with exactly one abstract method (SAM โ€” Single Abstract Method). It may have any number of default and static methods. The @FunctionalInterface annotation is optional but recommended โ€” it lets the compiler enforce the SAM rule. Functional interfaces are the target type for lambda expressions and method references.

FUNCTIONAL INTERFACE โ€” ANATOMY โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ @FunctionalInterface interface Greeting { String greet(String name); โ† the ONE abstract method (SAM) default void printGreet(String name) { // default โ€” OK, not abstract System.out.println(greet(name)); } static Greeting formal() { // static โ€” OK, not abstract return name -> "Good day, " + name; } } Rules: โœ” Exactly 1 abstract method โœ” Any number of default / static methods โœ” Methods inherited from java.lang.Object (equals, hashCode, toString) do NOT count as abstract methods even if re-declared โœ” @FunctionalInterface is optional but recommended (compiler enforcement)

โšก Lambda Expressions โ€” Syntax Evolution

LAMBDA SYNTAX โ€” FULL SHORTHAND PROGRESSION โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ // Full anonymous class (pre-Java 8) Comparator<String> c1 = new Comparator<String>() { @Override public int compare(String a, String b) { return a.compareTo(b); } }; // Lambda โ€” full form Comparator<String> c2 = (String a, String b) -> { return a.compareTo(b); }; // Lambda โ€” infer parameter types Comparator<String> c3 = (a, b) -> { return a.compareTo(b); }; // Lambda โ€” single expression (drop braces + return) Comparator<String> c4 = (a, b) -> a.compareTo(b); Shorthand rules: 1. Parameter types can be omitted (inferred from functional interface) 2. Parentheses can be omitted for exactly ONE parameter: n -> n * n 3. Braces + return can be omitted for single-expression body 4. Cannot omit parentheses for zero params: () -> "hello" 5. Cannot omit parentheses for two+ params: (a, b) -> a + b

๐Ÿ“ฆ Built-in Functional Interfaces (java.util.function)

THE 4 CORE FUNCTIONAL INTERFACES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Predicate<T> T โ†’ boolean test(T t) Function<T, R> T โ†’ R apply(T t) Consumer<T> T โ†’ void accept(T t) Supplier<T> () โ†’ T get() Derived variants: BiPredicate<T,U> (T,U) โ†’ boolean BiFunction<T,U,R> (T,U) โ†’ R BiConsumer<T,U> (T,U) โ†’ void UnaryOperator<T> T โ†’ T (Function where T==R) BinaryOperator<T> (T,T) โ†’ T IntPredicate int โ†’ boolean (primitive specialisation) IntFunction<R> int โ†’ R IntSupplier () โ†’ int IntConsumer int โ†’ void ToIntFunction<T> T โ†’ int Usage pattern: Predicate<String> isLong = s -> s.length() > 5; Function<String,Integer> len = String::length; // method ref Consumer<String> printer = System.out::println; // method ref Supplier<List> newList = ArrayList::new; // constructor ref

๐Ÿ”— Method References โ€” Four Forms

METHOD REFERENCE TYPES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. Static method ref ClassName::staticMethod Function<String,Integer> parse = Integer::parseInt; // same as: s -> Integer.parseInt(s) 2. Instance method ref instance::instanceMethod Consumer<String> print = System.out::println; // same as: s -> System.out.println(s) 3. Arbitrary instance ref ClassName::instanceMethod Function<String,Integer> len = String::length; // same as: s -> s.length() 4. Constructor ref ClassName::new Supplier<ArrayList> make = ArrayList::new; // same as: () -> new ArrayList() When to prefer method ref over lambda: โœ” When the lambda just calls a single existing method โœ” Improves readability: list.forEach(System.out::println) โœ— When the lambda does computation: x -> x * x + 1 (keep lambda)

๐Ÿ”„ Lambda vs Anonymous Class โ€” Key Differences

LAMBDA vs ANONYMOUS CLASS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Feature Lambda Anonymous Class โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ this keyword refers to ENCLOSING refers to anonymous class instance class instance itself Separate .class No (.class not created) Yes โ€” ClassName$1.class Effectively final Variables used must be Variables used must be requirement effectively final effectively final Can have state No โ€” no fields Yes โ€” can declare fields Target type Only FunctionalInterface Any interface or class Performance Slightly faster (invokedynamic) Slightly slower
๐Ÿ’ก Lambda captures "effectively final" variables
A lambda can reference local variables from its enclosing scope only if they are effectively final โ€” assigned exactly once, never changed after first assignment. You don't need the final keyword, but the variable must behave as if it were. This is because lambdas may be executed on a different thread โ€” mutating a shared local would cause a race condition. Instance fields and static fields have no such restriction.

๐Ÿ’ป Program 1 โ€” Custom Functional Interface + Lambda Evolution

LambdaEvolution.java
@FunctionalInterface
interface MathOperation {
    int operate(int a, int b);
    default int operateAndDouble(int a, int b) { return operate(a, b) * 2; }
}

public class LambdaEvolution {

    static int apply(int a, int b, MathOperation op) {
        return op.operate(a, b);
    }

    public static void main(String[] args) {

        // Step 1 โ€” anonymous class (old style)
        MathOperation addAnon = new MathOperation() {
            public int operate(int a, int b) { return a + b; }
        };

        // Step 2 โ€” lambda full form
        MathOperation addFull  = (int a, int b) -> { return a + b; };

        // Step 3 โ€” infer types
        MathOperation addInfer = (a, b) -> { return a + b; };

        // Step 4 โ€” drop braces + return
        MathOperation add      = (a, b) -> a + b;
        MathOperation multiply = (a, b) -> a * b;
        MathOperation power    = (a, b) -> (int) Math.pow(a, b);

        System.out.println("add(3,4)           = " + apply(3, 4, add));
        System.out.println("multiply(3,4)      = " + apply(3, 4, multiply));
        System.out.println("power(2,8)         = " + apply(2, 8, power));
        System.out.println("add.operateAndDouble(3,4) = " + add.operateAndDouble(3, 4));

        // Lambda as inline argument (no variable needed)
        System.out.println("inline sub(10,3)   = " + apply(10, 3, (a,b) -> a-b));
    }
}
โ–ถ OUTPUT
add(3,4) = 7 multiply(3,4) = 12 power(2,8) = 256 add.operateAndDouble(3,4) = 14 inline sub(10,3) = 7

๐Ÿ’ป Program 2 โ€” Built-in Functional Interfaces + Method References

BuiltInFunctional.java
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;

public class BuiltInFunctional {
    public static void main(String[] args) {

        // Predicate<T> โ€” T โ†’ boolean
        Predicate<String> isLong  = s -> s.length() > 5;
        Predicate<String> startA = s -> s.startsWith("A");
        Predicate<String> both   = isLong.and(startA);   // Predicate composition
        System.out.println("Predicate ---");
        System.out.println("  isLong("Hello"):  " + isLong.test("Hello"));
        System.out.println("  both("Alexander"):" + both.test("Alexander"));

        // Function<T,R> โ€” T โ†’ R
        Function<String, Integer> len    = String::length;  // method ref
        Function<Integer, String> stars  = n -> "*".repeat(n);
        Function<String, String> bar   = len.andThen(stars);  // compose
        System.out.println("Function ---");
        System.out.println("  len("Java"): " + len.apply("Java"));
        System.out.println("  bar("Hello"): " + bar.apply("Hello"));

        // Consumer<T> โ€” T โ†’ void
        Consumer<String> upper = s -> System.out.print(s.toUpperCase() + " ");
        Consumer<String> excl  = s -> System.out.print(s + "! ");
        System.out.println("
Consumer ---");
        List.of("java","lambda","stream")
            .forEach(upper.andThen(excl));  // chain consumers

        // Supplier<T> โ€” () โ†’ T
        Supplier<String> ts = () -> "generated@" + System.currentTimeMillis() % 10000;
        System.out.println("
Supplier ---");
        System.out.println("  " + ts.get());

        // Method references โ€” all 4 forms
        Function<String,Integer> parseInt   = Integer::parseInt;   // static ref
        Consumer<String>          printer   = System.out::println; // instance ref
        Function<String,String>  toUpper   = String::toUpperCase;  // arbitrary ref
        Supplier<ArrayList>      newList   = ArrayList::new;        // constructor ref
        System.out.println("
Method refs ---");
        System.out.println("  parseInt("42"): " + parseInt.apply("42"));
        printer.accept("  printer: hello");
        System.out.println("  toUpper("lambda"): " + toUpper.apply("lambda"));
        System.out.println("  newList: " + newList.get().getClass().getSimpleName());
    }
}
โ–ถ OUTPUT
Predicate --- isLong("Hello"): false both("Alexander"):true Function --- len("Java"): 4 bar("Hello"): ***** Consumer --- JAVA java! LAMBDA lambda! STREAM stream! Supplier --- generated@7341 Method refs --- parseInt("42"): 42 printer: hello toUpper("lambda"): LAMBDA newList: ArrayList

๐Ÿ’ป Program 3 โ€” Lambda Closure + Predicate/Function Chaining

LambdaChaining.java โ€” closure, compose, andThen, negate
import java.util.function.*;
import java.util.*;

public class LambdaChaining {
    public static void main(String[] args) {

        // Closure โ€” lambda captures effectively final local variable
        int threshold = 100;                        // effectively final
        Predicate<Integer> isAbove = n -> n > threshold;  // captures threshold
        System.out.println("150 above 100? " + isAbove.test(150));
        System.out.println("50  above 100? " + isAbove.test(50));

        // Predicate composition: and, or, negate
        Predicate<Integer> isEven  = n -> n % 2 == 0;
        Predicate<Integer> isPos   = n -> n > 0;
        Predicate<Integer> posEven = isEven.and(isPos);
        Predicate<Integer> isOdd   = isEven.negate();
        List<Integer> nums = List.of(-4, -3, 0, 2, 5, 6, 9);
        System.out.print("positive evens: ");
        nums.stream().filter(posEven).forEach(n -> System.out.print(n + " "));
        System.out.print("
odds:          ");
        nums.stream().filter(isOdd).forEach(n -> System.out.print(n + " "));

        // Function compose vs andThen
        Function<Integer,Integer> times2   = x -> x * 2;
        Function<Integer,Integer> plus10   = x -> x + 10;
        // andThen: times2 first, then plus10  โ†’ (5*2)+10 = 20
        // compose: plus10 first, then times2  โ†’ (5+10)*2 = 30
        System.out.println("
times2.andThen(plus10).apply(5) = "
            + times2.andThen(plus10).apply(5));
        System.out.println("times2.compose(plus10).apply(5) = "
            + times2.compose(plus10).apply(5));
    }
}
โ–ถ OUTPUT
150 above 100? true 50 above 100? false positive evens: 2 6 odds: -3 5 9 times2.andThen(plus10).apply(5) = 20 times2.compose(plus10).apply(5) = 30
โš ๏ธ Lambda does NOT create a .class file
Unlike anonymous inner classes (which generate Outer$1.class), lambdas use the JVM invokedynamic instruction introduced in Java 7. The lambda body is compiled as a synthetic private static method in the enclosing class โ€” no separate class file is created. This makes lambdas lighter on class loading and faster to instantiate than anonymous classes.
โญ Must-Know Interview Topics
SAM rule ยท @FunctionalInterface ยท lambda syntax shorthand rules ยท lambda vs anonymous class (this, .class, performance) ยท effectively final ยท 4 core interfaces (Predicate/Function/Consumer/Supplier) ยท 4 method reference forms ยท Predicate.and/or/negate ยท Function.andThen/compose ยท invokedynamic
Q1. What is a functional interface? Can it have more than one method?
A functional interface has exactly one abstract method (the SAM). It can have:
โ€” Any number of default methods
โ€” Any number of static methods
โ€” Re-declarations of java.lang.Object methods (equals, hashCode, toString) โ€” these do NOT count as additional abstract methods

@FunctionalInterface is optional but strongly recommended โ€” it instructs the compiler to enforce the SAM rule and makes intent clear. Examples from JDK: Runnable (run), Callable (call), Comparator (compare), Predicate (test), Function (apply).
Q2. What does "effectively final" mean in the context of lambdas?
A local variable is effectively final if it is assigned exactly once and never reassigned or modified after that point. You don't need the final keyword โ€” the compiler infers it.

A lambda can only capture local variables that are effectively final because lambdas may be passed to other threads and executed later. If the lambda could read a mutable local variable that's changing on the original thread, you'd have a race condition with no synchronisation.

Instance fields and static fields are not subject to this rule โ€” they are accessed through the heap (not the stack frame) and their thread-safety is the programmer's responsibility via normal synchronisation.

Attempting to capture a non-final local variable gives the compile error: "Variable used in lambda expression should be final or effectively final."
Q3. How is a lambda different from an anonymous class?
Key differences:

this keyword: In a lambda, this refers to the enclosing class instance. In an anonymous class, this refers to the anonymous class instance itself.

Class file: An anonymous class generates a separate Outer$1.class file. A lambda does not โ€” it is compiled as a synthetic method using invokedynamic.

Performance: Lambdas are slightly faster to load and instantiate than anonymous classes due to invokedynamic deferring implementation to runtime.

Target type: A lambda can only implement a functional interface. An anonymous class can implement any interface (including multi-method ones) or extend any class.

State: An anonymous class can declare its own fields. A lambda cannot โ€” it captures variables from the enclosing scope only.
Q4. What are the four core functional interfaces and their SAMs?
Predicate<T>: boolean test(T t) โ€” takes T, returns boolean. Used for filtering. Composed with and(), or(), negate().

Function<T, R>: R apply(T t) โ€” takes T, returns R. Used for transformations. Composed with andThen() (apply this then that) and compose() (apply that then this).

Consumer<T>: void accept(T t) โ€” takes T, returns nothing. Used for side effects (printing, saving). Chained with andThen().

Supplier<T>: T get() โ€” takes nothing, returns T. Used for lazy evaluation, factory methods, deferred computation.
Q5. What are the four forms of method references?
1. Static method reference: ClassName::staticMethod โ€” e.g., Integer::parseInt is s -> Integer.parseInt(s)

2. Instance method of a specific object: instance::method โ€” e.g., System.out::println is s -> System.out.println(s)

3. Instance method of an arbitrary object of a type: ClassName::instanceMethod โ€” e.g., String::length is s -> s.length(). The first argument becomes the receiver.

4. Constructor reference: ClassName::new โ€” e.g., ArrayList::new is () -> new ArrayList()

Use method references when the lambda body is just a direct call to a single existing method โ€” they are more readable and convey the same intent with less noise.
Q6. What is the difference between Function.andThen() and Function.compose()?
Both compose two Functions together into a pipeline, but in opposite orders:

andThen(after): apply this function first, then pass the result to after.
f.andThen(g).apply(x) = g(f(x))

compose(before): apply before first, then pass the result to this function.
f.compose(g).apply(x) = f(g(x))

Example with times2.andThen(plus10) vs times2.compose(plus10) on input 5:
โ€” andThen: (5ร—2)+10 = 20
โ€” compose: (5+10)ร—2 = 30

Memory aid: andThen reads left-to-right (f then g); compose reads right-to-left (g then f).

๐ŸŒฑ Lambdas & Functional Interfaces Throughout Spring Boot

Lambda expressions are the backbone of modern Spring Boot code. They appear in stream pipelines, Spring Security configuration, event listeners, bean definitions, and more.

โœ… Where lambdas power Spring Boot
Stream API โ€” list.stream().filter(...).map(...).collect(...) everywhere
Spring Security (lambda DSL) โ€” http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
@Bean definitions โ€” @Bean public PasswordEncoder encoder() { return new BCryptPasswordEncoder(); }
ApplicationEventListener โ€” context.addApplicationListener(event -> log.info("started"))
RestTemplate / WebClient โ€” webClient.get().retrieve().bodyToMono(String.class) uses Function chains
Specification API (JPA) โ€” Specification<User> spec = (root, query, cb) -> cb.equal(root.get("active"), true)
Spring Security Lambda DSL + JPA Specification + Stream pipeline
// โ”€โ”€ Spring Security 6 โ€” lambda DSL โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .authorizeHttpRequests(auth -> auth            // Consumer<AuthorizationManagerRequestMatcherRegistry>
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated())
            .formLogin(form -> form                           // Consumer<FormLoginConfigurer>
                .loginPage("/login").permitAll())
            .build();
    }
}

// โ”€โ”€ JPA Specification โ€” lambda implements Specification<T> โ”€โ”€โ”€
public class UserSpec {
    public static Specification<User> isActive() {
        return (root, query, cb) ->            // lambda = SAM toPredicate()
            cb.isTrue(root.get("active"));
    }
    public static Specification<User> nameContains(String q) {
        return (root, query, cb) ->
            cb.like(cb.lower(root.get("name")), "%" + q.toLowerCase() + "%");
    }
}
// Usage: userRepo.findAll(isActive().and(nameContains("alice")))

// โ”€โ”€ Stream pipeline in service layer โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@Service
public class OrderService {
    public Map<String, List<Order>> groupByStatus(List<Order> orders) {
        return orders.stream()
            .filter(o -> o.getTotal() > 0)            // Predicate
            .sorted(Comparator.comparing(Order::getDate))  // method ref
            .collect(Collectors.groupingBy(Order::getStatus));  // method ref
    }
}
๐Ÿ“Œ Spring Security migrated from method chaining to Lambda DSL in Spring 6
The old style was http.authorizeRequests().antMatchers("/admin").hasRole("ADMIN").and().formLogin(). Spring 6 replaced this with the lambda DSL where every configurer takes a Consumer<T> โ€” each block is a lambda. This is cleaner, fully IDE-navigable, and avoids the confusing .and() connector calls. Understanding functional interfaces is essential to reading modern Spring Security configurations.
โญ Must-Know Exam Points
  • Functional interface = exactly 1 abstract method (SAM); default/static don't count
  • @FunctionalInterface is optional but enforces SAM at compile time
  • Object methods (equals, hashCode, toString) do NOT count as abstract methods
  • Lambda shorthand: omit types โ†’ omit parens (1 param) โ†’ omit braces+return (1 expr)
  • Zero params: () -> ... โ€” parentheses mandatory
  • Two+ params: (a, b) -> ... โ€” parentheses mandatory
  • Lambda captures effectively final locals only; instance/static fields unrestricted
  • Lambda this = enclosing class; anonymous class this = anonymous class itself
  • Lambda does NOT generate a separate .class file (uses invokedynamic)
  • Predicate: test() ยท Function: apply() ยท Consumer: accept() ยท Supplier: get()
  • andThen: f then g ยท compose: g then f (opposite order)
  • 4 method reference forms: static, specific instance, arbitrary instance, constructor

๐Ÿ“ MCQ Traps

QuestionAnswerTrap / Reason
Interface with 2 abstract methods โ€” valid @FunctionalInterface?No โ€” compile errorSAM rule: exactly 1 abstract method required
Interface re-declares Object.equals() โ€” does it count as abstract?No โ€” does not countObject method re-declarations are excluded from SAM count
Can lambda capture a local variable that changes after lambda creation?No โ€” compile errorVariable must be effectively final; modification after assignment disqualifies it
In lambda body, what does this refer to?The enclosing class instanceUnlike anonymous class, lambda does not introduce a new this scope
f.andThen(g).apply(5) โ€” which runs first?f runs first, then gandThen = "apply this, THEN the argument"; compose is the reverse
Lambda for zero-param method โ€” can you write: x -> "hi"?No โ€” must be: () -> "hi"Zero params requires empty parentheses; x -> ... implies one parameter named x
Does lambda create a new .class file like anonymous class?No โ€” uses invokedynamicLambda is compiled as synthetic method; no Outer$1.class is generated

๐Ÿ“– One-Liner Definitions

  • Functional Interface โ€” An interface with exactly one abstract method (SAM); the target type for lambda expressions and method references.
  • Lambda Expression โ€” An anonymous function implementing a functional interface's SAM; syntax: (params) -> body; compiled via invokedynamic without generating a .class file.
  • Method Reference โ€” Shorthand for a lambda that calls a single existing method; four forms: static, instance, arbitrary instance, constructor.
  • Effectively Final โ€” A local variable assigned exactly once; can be captured by a lambda without the final keyword.
  • Predicate<T> โ€” Functional interface: boolean test(T t); used for boolean conditions, filtering.
  • Function<T,R> โ€” Functional interface: R apply(T t); used for transformations; composable via andThen/compose.
  • Consumer<T> โ€” Functional interface: void accept(T t); used for side-effecting operations.
  • Supplier<T> โ€” Functional interface: T get(); used for lazy/deferred value production.
๐ŸŽ‰
All 30 Topics Complete!
You have covered the complete OOPs in Java playlist:
Introduction โ†’ Classes & Objects โ†’ Methods โ†’ Memory โ†’ Arrays โ†’ Static โ†’ Encapsulation โ†’ this โ†’ Constructors โ†’ Copy Constructor โ†’ Singleton โ†’ Naming โ†’ Anonymous Objects โ†’ Inheritance โ†’ super โ†’ Overriding โ†’ Packages โ†’ Access Modifiers โ†’ Polymorphism โ†’ final โ†’ Object Class โ†’ Casting โ†’ Abstraction โ†’ Inner Classes โ†’ Interfaces โ†’ Interface Modern Features โ†’ Enum โ†’ Annotations โ†’ Var-Args โ†’ Functional Interface & Lambda
โ˜…

Master Summary โ€” Complete OOPs Quick Reference

๐Ÿ›๏ธ The Four Pillars of OOP

PillarOne-Line DefinitionJava MechanismReal-World Analogy
EncapsulationWrap data + behaviour; hide internalsprivate fields + getters/settersCapsule pill โ€” medicine inside, safe exterior
InheritanceChild reuses parent's code (IS-A)extends keywordChild inherits traits from parent
PolymorphismOne interface, many formsOverloading + Overriding + DMDA person is employee at work, parent at home
AbstractionShow essential, hide complexityabstract class + interfaceCar dashboard โ€” you drive without knowing engine

๐Ÿ”‘ All 30 Topics โ€” Condensed Reference

#TopicKey PointMust-Know
01Intro to OOP4 pillars: APIE. OOP models real world as objectsProcedural vs OOP tradeoffs
02Classes & ObjectsClass = blueprint (no memory); Object = instance (Heap)Reference copy trap; dot operator
03MethodsOverloading by type/count/order of params; pass-by-value alwaysMethod chaining; return types
04Memory ManagementStack = local vars + frames; Heap = objectsGC handles Heap; Stack auto-cleared on return
05ArraysObject on Heap; .length field; 0-indexed; ArrayIndexOutOfBoundsObject array default = null
06StaticClass-level (not instance); static block runs once at class loadCannot access instance vars from static context
07Encapsulationprivate fields + public getters/setters; data hidingEncapsulation โ‰  Abstraction
08this keywordCurrent object ref; this() constructor chain; resolves shadowingthis() must be first statement
09ConstructorsSame name as class; no return type; auto default if none definedOverloading; default disappears when param constructor added
10Copy ConstructorManual in Java; shallow vs deep copyJava has no built-in copy constructor unlike C++
11SingletonPrivate constructor + static instance; Enum Singleton is bestDouble-checked locking; volatile keyword
12Naming ConventionsPascalCase (class), camelCase (method/var), ALL_CAPS (constant)Package = reverse domain lowercase
13Anonymous ObjectsObject without reference; single-use; immediately GC-eligibleAnonymous object โ‰  anonymous class
14Inheritanceextends; IS-A; single + multilevel + hierarchical; no multipleDiamond Problem โ†’ no multiple class inheritance in Java
15super KeywordParent field/method/constructor; implicit super() in every constructorsuper() must be first statement; Object is root
16Method OverridingSame name+params in subclass; runtime decision; @OverrideCannot override static/final/private; covariant return OK
17Packagespackage keyword; import; java.lang auto-imported; reverse domainWildcard import doesn't import sub-packages
18Access Modifiersprivate < default < protected < publicprotected = package + subclass (even different package)
19PolymorphismCompile-time (overloading) vs Runtime (overriding + DMD)Parent ref + child object โ†’ parent ref cannot access child-only methods
20finalfinal var = constant; final method = no override; final class = no extendBlank final must be assigned in constructor
21Object ClassRoot of all classes; toString/equals/hashCode contractequals+hashCode must be consistent; == vs equals
22CastingUpcasting implicit (safe); downcasting explicit (risky)instanceof check before downcast; ClassCastException
23Abstractionabstract class: partial; interface: full contractAbstract class can have constructors; cannot instantiate
24Inner ClassesNon-static (accesses outer); static (independent); anonymous (inline)Static inner class = no outer instance needed
25InterfacesContract; implements; default public abstract / public static finalMultiple interface implementation solves multiple inheritance
26Interface Moderndefault (Java 8, inherited), static (Java 8, not inherited), private (Java 9)Diamond problem โ†’ must override; B.super.method() syntax
27Enumfinal class extends java.lang.Enum; constants = static final singletonsCannot extend; can implement; constructor always private
28AnnotationsMetadata (@interface); @Retention / @Target configure behaviourRUNTIME retention needed for reflection; default is CLASS
29Var-ArgsType... syntax; treated as array; must be last parameter; one per methodOverloading priority: exact > widening > vararg
30Lambda & FunctionalLambda = anonymous function; requires @FunctionalInterface (SAM)Effectively final capture; method references (4 forms)

๐Ÿ†š Critical Comparisons at a Glance

ComparisonABKey Distinction
Overloading vs OverridingCompile-time; same class; different paramsRuntime; subclass; same signatureOverloading = ad hoc polymorphism; Overriding = subtype polymorphism
Abstract class vs InterfacePartial impl; constructor; single inheritFull contract; no constructor; multiple implementAbstract = IS-A with shared state; Interface = CAN-DO contract
== vs equals()Reference equality (same object?)Logical equality (same value?)Always override equals() for value classes; use == for enum/null check
Stack vs HeapLocal vars, method frames; LIFO; auto-freedObjects, instance vars; GC-managedStack overflow = deep recursion; Heap OutOfMemory = too many objects
this() vs super()Calls another constructor in same classCalls parent class constructorBoth must be FIRST statement; cannot use both in same constructor
final vs finally vs finalizeModifier (var/method/class)try-catch block; finalize() = GC callback (deprecated)Three completely different concepts sharing a name prefix
Checked vs Unchecked ExceptionMust handle/declare (IOException)RuntimeException (NullPointerException)Checked = recoverable; Unchecked = programming bug
Shallow vs Deep CopyCopies references to nested objectsRecursively copies all nested objectsShallow = fast but shared state; Deep = safe but expensive

๐Ÿ“ Access Modifier Quick Reference

ModifierSame ClassSame PackageSubclass (diff pkg)Everywhere
privateโœ…โŒโŒโŒ
defaultโœ…โœ…โŒโŒ
protectedโœ…โœ…โœ…โŒ
publicโœ…โœ…โœ…โœ…

๐Ÿ”— Inheritance Rules Summary

WHAT CAN BE INHERITED / OVERRIDDEN โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ CAN inherit: public/protected fields, methods, inner classes CANNOT inherit: private members, constructors CAN override: public/protected instance methods CANNOT override: static methods (hidden, not overridden) final methods private methods constructors COVARIANT RETURN: overriding method CAN narrow the return type EXCEPTION: overriding method CANNOT throw broader checked exceptions POLYMORPHISM RULE: Animal a = new Dog(); // upcasting (implicit) a.speak(); // calls Dog.speak() โ€” runtime binding a.fetch(); // COMPILE ERROR โ€” not in Animal ref type
โญ Top 50 OOPs Interview Questions โ€” Complete Answers
Covers all major Java OOPs interview topics. Answers are concise and interview-ready.

๐Ÿ›๏ธ Core OOP Concepts (Q1โ€“Q10)

Q1. What are the four pillars of OOP?
Encapsulation โ€” binding data and methods; hiding internal state via private fields + public accessors.
Inheritance โ€” child class reuses parent's code via extends; IS-A relationship.
Polymorphism โ€” one interface, many forms: compile-time (overloading) and runtime (overriding + DMD).
Abstraction โ€” exposing only essential features; hiding implementation via abstract classes and interfaces.
Q2. What is the difference between a class and an object?
A class is a blueprint/template โ€” it defines structure and behaviour but occupies no heap memory by itself. An object is an instance of a class created at runtime using new; it resides on the Heap and has its own copy of instance variables. Multiple objects can be created from one class, each with independent state.
Q3. What is encapsulation and why is it important?
Encapsulation bundles data (fields) and behaviour (methods) into one unit and restricts direct access to fields using private. Access is controlled through public getters/setters. Benefits: data validation in setters, hiding implementation details, reducing coupling, making code easier to maintain and test without breaking callers.
Q4. What is the difference between abstraction and encapsulation?
Abstraction is about design โ€” what operations does this type expose? It hides complexity by showing only essential features (achieved via abstract classes and interfaces).
Encapsulation is about implementation โ€” how is data protected? It hides the internal state by restricting field access (achieved via access modifiers).
Analogy: A TV's remote control is the abstraction (you know what buttons do); the circuit board inside is encapsulated (hidden, protected).
Q5. What is polymorphism? Name its types.
Polymorphism means one name/interface behaving differently in different contexts.
Compile-time (Static) polymorphism โ€” method overloading; resolved at compile time based on parameter types.
Runtime (Dynamic) polymorphism โ€” method overriding + Dynamic Method Dispatch; resolved at runtime based on actual object type.
Example: Animal a = new Dog(); a.speak(); โ€” the JVM calls Dog.speak() at runtime even though the reference is Animal.
Q6. What is the difference between method overloading and overriding?
Overloading: same method name, different parameter list (type/count/order), in the same class. Resolved at compile time. Return type alone cannot distinguish overloads.
Overriding: same method name AND same parameter list, in a subclass. Resolved at runtime via DMD. Return type can be covariant (narrowed). Cannot override static/final/private methods.
Q7. What is Dynamic Method Dispatch?
DMD is the mechanism by which Java decides, at runtime, which overridden method to call based on the actual type of the object โ€” not the declared type of the reference.
Example: Animal a = new Cat(); When a.speak() is called, Java looks at the actual object (Cat) and calls Cat.speak(), even though the reference is typed Animal. This is the engine behind runtime polymorphism.
Q8. What is inheritance? What are its types in Java?
Inheritance allows a child class to acquire fields and methods of a parent class using extends (IS-A relationship).
Single: one child, one parent.
Multilevel: A โ†’ B โ†’ C (chain).
Hierarchical: one parent, multiple children.
Multiple inheritance via class: NOT supported (Diamond Problem). Solved via multiple interface implementation.
Q9. Why does Java not support multiple class inheritance?
Because of the Diamond Problem: if class C inherits from both A and B, and both A and B override the same method from a common ancestor, the JVM cannot determine which version to use โ€” creating ambiguity. Java avoids this entirely by allowing only single class inheritance. Multiple inheritance of type is achieved through interfaces (and since Java 8, the Diamond Problem for default methods is resolved explicitly by the class using B.super.method()).
Q10. What is the difference between IS-A and HAS-A relationships?
IS-A โ€” inheritance relationship. A Dog IS-A Animal. Modelled with extends (class) or implements (interface). Tightly couples child to parent.
HAS-A โ€” composition/aggregation. A Car HAS-A Engine. Modelled by holding a reference to another object as a field. Looser coupling; generally preferred over deep inheritance hierarchies ("favour composition over inheritance").

๐Ÿ”ง Constructors, this & super (Q11โ€“Q18)

Q11. What is a constructor? How is it different from a method?
A constructor initialises a newly created object. Differences: name must match class name exactly; has no return type (not even void); called automatically by new; cannot be abstract, final, static, or synchronized. A method is a named block of behaviour with a return type, called explicitly.
Q12. What is a default constructor? When does the compiler add it?
A default (no-arg) constructor is automatically inserted by the compiler only if the class defines no constructors at all. The moment you declare any constructor (parameterised or otherwise), the compiler stops generating the default one. If you then need a no-arg constructor, you must write it explicitly.
Q13. What is constructor chaining? What are the rules?
Constructor chaining means one constructor calls another. this(args) calls a sibling constructor in the same class. super(args) calls the parent's constructor. Rules: the chaining call must be the first statement in the constructor body; you cannot use both this() and super() in the same constructor; every constructor implicitly calls super() if no explicit chain call is present.
Q14. What are the uses of the this keyword?
1. Resolve shadowing โ€” this.name = name distinguishes field from parameter.
2. Constructor chaining โ€” this(args) calls another constructor in the same class (must be first statement).
3. Pass current object โ€” someMethod(this) passes the current object as an argument.
4. Return current object โ€” enables method chaining (builder pattern).
5. Non-static context only โ€” cannot use this inside a static method.
Q15. What is the difference between this() and super()?
this() โ€” calls another constructor within the same class. Used for constructor overload chaining.
super() โ€” calls a constructor in the parent class. Used to initialise inherited state. Every constructor has an implicit super() unless this() or an explicit super() is provided. Both must be the first statement, so they are mutually exclusive in the same constructor body.
Q16. Can a constructor be private? What is the use?
Yes. A private constructor prevents external code from instantiating the class using new. Primary uses: Singleton pattern (controlled single instance via static factory method), utility/helper classes (like java.lang.Math โ€” no need to instantiate), and static factory method pattern (object creation through named static methods).
Q17. What is a copy constructor?
A constructor that takes an object of the same class as a parameter and initialises the new object with copies of the original's field values. Java has no built-in copy constructor (unlike C++); you must write it manually. Shallow copy duplicates field values (references share nested objects); deep copy recursively duplicates nested objects for full independence.
Q18. What is the Singleton pattern and how do you implement it?
Singleton ensures a class has exactly one instance. Key elements: private constructor, private static instance field, public static factory method. Thread-safe implementations: Eager (static final field โ€” simple, always safe), Double-checked locking (lazy + volatile), Bill Pugh / static holder (lazy + thread-safe without synchronisation). Best option: enum Singleton { INSTANCE; } โ€” JVM guarantees one instance, serialisation-safe, reflection-safe.

๐Ÿ”Œ Interfaces & Abstract Classes (Q19โ€“Q28)

Q19. What is the difference between abstract class and interface?
Abstract class: can have concrete methods, constructors, instance fields, any access modifiers; supports single inheritance.
Interface: traditionally pure contract (all abstract); since Java 8 can have default/static methods; since Java 9 private methods; fields are public static final; no constructors; supports multiple implementation.
Use abstract class when sharing code among closely related classes (IS-A + shared state).
Use interface when defining a capability/contract unrelated to class hierarchy (CAN-DO).
Q20. Can you instantiate an abstract class?
No โ€” a new AbstractClass() directly causes a compile error. However, you can instantiate a concrete subclass and hold it in an abstract class reference (AbstractClass ref = new ConcreteSubclass();). You can also create an anonymous class inline that provides implementations for all abstract methods โ€” this effectively creates an unnamed subclass instance.
Q21. What is a functional interface?
An interface with exactly one abstract method (SAM โ€” Single Abstract Method). It can have any number of default and static methods. Annotated with @FunctionalInterface (optional but recommended โ€” enforces SAM at compile time). Required for lambda expressions. Built-in examples: Runnable, Callable, Predicate<T>, Function<T,R>, Consumer<T>, Supplier<T>, Comparator<T>.
Q22. What are default methods in interfaces and why were they added?
Default methods (Java 8) are interface methods with a complete body, prefixed with default. Implementing classes inherit them automatically and can optionally override. They were added for backward compatibility: the JDK needed to add new methods (like Collection.stream(), Iterable.forEach()) to existing widely-implemented interfaces without breaking every class that already implemented them.
Q23. How is the Diamond Problem resolved for default methods?
Java applies three rules in order: (1) Class wins โ€” a class's concrete method overrides any default method. (2) Specific interface wins โ€” if B extends A and both provide the same default, B's version wins. (3) Must override โ€” if neither rule resolves ambiguity (two unrelated interfaces), the implementing class must provide its own override, optionally delegating: B.super.methodName().
Q24. Can an interface have a constructor?
No. Interfaces cannot have constructors because they cannot be instantiated directly. A constructor's purpose is to initialise object state, but interfaces have no instance state (fields are implicitly public static final โ€” class-level constants). Since Java 9, interfaces can have private methods for internal code reuse, but still no constructors.
Q25. What is marker interface?
A marker interface is an empty interface โ€” it has no methods or fields. Its sole purpose is to tag a class so that the JVM or framework can treat it specially at runtime via instanceof. Classic Java examples: Serializable (marks class as serialisable), Cloneable (allows Object.clone()), RandomAccess. Modern Java prefers annotations for this purpose, but marker interfaces still exist for type-safety reasons.
Q26. What is the difference between interface and abstract class for designing APIs?
Use interface for public APIs intended to be implemented by unrelated classes โ€” it gives implementors freedom (no inheritance chain imposed) and allows multiple implementation. Use abstract class when you need a template method pattern โ€” shared algorithm skeleton with customisable steps โ€” or when sharing state (fields) and code (concrete methods) among closely related subtypes. Rule of thumb: prefer interfaces for type definitions; use abstract classes for code sharing within a family.
Q27. What are inner classes? What types exist?
Non-static inner class: tied to outer class instance; can access all outer members including private; requires outer instance to instantiate.
Static nested class: declared static; no outer instance needed; can only access outer static members directly.
Local class: defined inside a method; can access effectively final local variables.
Anonymous class: nameless inline class; common for one-off interface implementations; replaced by lambdas for functional interfaces.
Q28. What is upcasting and downcasting?
Upcasting: assigning a subclass object to a superclass reference โ€” implicit and always safe. Animal a = new Dog();
Downcasting: casting a superclass reference back to a subclass type โ€” explicit and potentially unsafe (throws ClassCastException at runtime if the object is not actually the target type).
Dog d = (Dog) a; โ€” always check with instanceof first. Java 16+ pattern matching: if (a instanceof Dog d) { d.fetch(); }

๐Ÿ”ค Keywords & Modifiers (Q29โ€“Q36)

Q29. What is the final keyword? Give three uses.
final variable: can be assigned only once; if primitive, value is constant; if reference, the reference cannot change (but object's internals can). Blank final must be initialised in constructor.
final method: cannot be overridden in any subclass.
final class: cannot be extended. Examples: String, Integer, all wrapper classes are final.
Q30. What is the static keyword? Can static methods access instance variables?
Static members belong to the class, not to any instance. A static variable is shared across all objects. A static method can be called without creating an object.
No โ€” static methods cannot directly access instance (non-static) variables or call instance methods, because there is no implicit this reference. They can access static fields and other static methods. Workaround: pass an object reference as a parameter.
Q31. What is the difference between == and equals()?
== compares references (memory addresses) for objects โ€” are they the exact same object in memory? For primitives, it compares values.
equals() compares logical content โ€” are the objects meaningfully equal? The default Object.equals() uses ==; you override it to define value-based equality (e.g., two String objects with the same characters).
Always override hashCode() when overriding equals() โ€” they must be consistent.
Q32. What is the Object class? Name its important methods.
Every Java class implicitly extends java.lang.Object โ€” it is the root of the entire class hierarchy. Key methods: toString() (default: className@hashCode โ€” override for meaningful representation), equals(Object) (default: reference equality โ€” override for value equality), hashCode() (must be consistent with equals), clone() (shallow copy โ€” class must implement Cloneable), getClass() (runtime class info), wait()/notify()/notifyAll() (thread coordination).
Q33. What are access modifiers in Java?
Four levels, from most to least restrictive: private (same class only), default/package-private (same package), protected (same package + subclasses in any package), public (everywhere). For top-level classes only public and default are allowed. Best practice: use the most restrictive access possible โ€” prefer private fields, package-private or protected for internal use, public only for intentional API.
Q34. What is the difference between static block and instance block?
Static block static { ... }: runs once when the class is first loaded by the JVM, before any object is created or main() runs. Used for one-time static initialisation (loading a driver, reading config).
Instance initialiser block { ... }: runs every time a new object is created, before the constructor body but after the super() call. Copied into every constructor by the compiler. Used for shared initialisation logic across overloaded constructors.
Q35. What is var-arg (varargs)? What are the rules?
Varargs (type... name) allows a method to accept zero or more arguments of a type without the caller building an array explicitly. The JVM treats it as an array internally. Rules: (1) must be the last parameter; (2) only one vararg per method; (3) can pass an explicit array. Overload resolution priority: exact match > widening > varargs (varargs are chosen last). printf and String.format are classic vararg methods.
Q36. What is the difference between String, StringBuilder, and StringBuffer?
String: immutable; every modification creates a new object; thread-safe by design; lives in String Pool.
StringBuilder: mutable; no synchronisation; not thread-safe; fastest for single-threaded string building.
StringBuffer: mutable; all methods synchronized; thread-safe; slower due to synchronisation overhead.
Use StringBuilder for most cases; StringBuffer only when shared across threads.

โš™๏ธ Enum, Annotations & Lambdas (Q37โ€“Q44)

Q37. What is an enum in Java? Is it a class?
Yes โ€” an enum is a special final class that implicitly extends java.lang.Enum. Each constant is a public static final singleton instance created when the class loads. Enum can have fields, a private constructor, and methods. It cannot extend another class but can implement interfaces. Constants are compared with == (singletons). The single-constant enum (enum Singleton { INSTANCE; }) is the best Singleton implementation.
Q38. What is an annotation? How do you create a custom one?
An annotation is metadata attached to code using @; it does not change logic but provides instructions to the compiler, build tools, or runtime framework. Define with @interface; configure with meta-annotations: @Retention (SOURCE/CLASS/RUNTIME), @Target (where it can be placed). Read at runtime via reflection: method.getAnnotation(MyAnnotation.class). Elements can only be primitives, String, Class, enum, annotation, or 1D arrays of these.
Q39. What is a lambda expression in Java?
A lambda is a concise anonymous function: (params) -> body. It provides an implementation of a functional interface (SAM) inline, without creating a named class. Lambdas capture effectively final local variables from the enclosing scope. They do not generate a separate .class file (handled by invokedynamic at the bytecode level). Examples: () -> System.out.println("hi"), x -> x * 2, (a, b) -> a + b.
Q40. What are method references? Give all four forms.
Method references are shorthand for lambdas that simply call an existing method: ClassName::methodName.
1. Static method ref: Integer::parseInt โ†’ s -> Integer.parseInt(s)
2. Instance method ref (bound): str::toUpperCase โ†’ () -> str.toUpperCase()
3. Instance method ref (unbound): String::toUpperCase โ†’ s -> s.toUpperCase()
4. Constructor ref: ArrayList::new โ†’ () -> new ArrayList()
Q41. What is effectively final in lambdas?
A local variable is "effectively final" if it is never reassigned after its initial assignment โ€” even if it is not explicitly declared final. Lambdas can capture only effectively final local variables from the enclosing scope. This restriction exists because lambdas may outlive the stack frame where the variable was declared; to prevent stale/inconsistent data, Java requires the captured value to be stable. Modifying a captured variable after the lambda is defined causes a compile error.
Q42. Name the key built-in functional interfaces in java.util.function.
Predicate<T>: boolean test(T t) โ€” boolean condition / filtering.
Function<T,R>: R apply(T t) โ€” transformation; composable.
Consumer<T>: void accept(T t) โ€” side-effecting operation.
Supplier<T>: T get() โ€” deferred / lazy value production.
BiFunction<T,U,R>: two inputs, one output.
UnaryOperator<T>: Function where T == R.
BinaryOperator<T>: BiFunction where all types == T.
Q43. What is the difference between Comparable and Comparator?
Comparable<T>: defines the class's natural ordering; implemented by the class itself via int compareTo(T other); one ordering per class.
Comparator<T>: external, custom ordering; passed to sort methods; multiple comparators possible for the same class; functional interface (single compare(T o1, T o2)) โ€” can be written as a lambda. Use Comparable for the default sort; use Comparator for alternate orderings.
Q44. What is the difference between throw and throws?
throw: a statement inside a method body that actually throws an exception instance: throw new IllegalArgumentException("msg");. Execution stops at this point.
throws: a clause in the method signature that declares which checked exceptions the method may propagate to the caller: void readFile() throws IOException. Callers must handle or re-declare the exception. throws is a declaration; throw is the action.

๐Ÿงฉ Miscellaneous & Advanced (Q45โ€“Q50)

Q45. What is the difference between shallow copy and deep copy?
Shallow copy: copies primitive field values directly; for reference fields, copies the reference (both original and copy point to the same nested object). Changes to nested objects in the copy affect the original.
Deep copy: recursively copies all nested objects so the copy is completely independent. Achieved via manual copy constructor, serialisation/deserialisation, or libraries like Apache Commons Lang's SerializationUtils.clone(). Deep copy is safer but more expensive.
Q46. What is cohesion and coupling in OOP?
Cohesion: how closely the responsibilities within a single class/module are related. High cohesion = one focused purpose = good. A class that handles both PDF parsing and email sending has low cohesion.
Coupling: degree of dependency between classes. Low coupling = classes can change independently = good. High coupling = change in one class breaks many others = bad.
Goal: High cohesion, low coupling. Achieved through encapsulation, interfaces, dependency injection.
Q47. What are the SOLID principles?
S โ€” Single Responsibility: one class, one reason to change.
O โ€” Open/Closed: open for extension, closed for modification (use inheritance/interfaces).
L โ€” Liskov Substitution: subclass must be usable wherever parent is expected without breaking logic.
I โ€” Interface Segregation: prefer many small interfaces over one fat interface.
D โ€” Dependency Inversion: depend on abstractions (interfaces), not concrete implementations.
Q48. What is the difference between aggregation and composition?
Both are HAS-A relationships (object contains another object).
Aggregation (weak HAS-A): the contained object can exist independently of the container. Example: University HAS-A Department โ€” Department can exist without the University object in memory.
Composition (strong HAS-A): the contained object's lifecycle is owned by the container โ€” it cannot exist independently. Example: House HAS-A Room โ€” if the House object is destroyed, Rooms are destroyed too. Composition implies complete ownership.
Q49. Can we override the main method in Java?
You can overload main (different parameters) but you cannot meaningfully override it for JVM entry purposes. The JVM always calls exactly public static void main(String[] args) as the entry point โ€” static methods are not overridden (they are hidden). A subclass can declare its own main but the parent's main is still a distinct static method, not polymorphically dispatched. So: overloading yes, polymorphic overriding no.
Q50. What is the difference between an interface and an abstract class โ€” when to use which?
Use interface when: defining a CAN-DO capability across unrelated classes (Flyable, Serializable); you want multiple implementation; you are designing a public API; the type has no shared state.
Use abstract class when: sharing code (concrete methods) among closely related classes; sharing state (instance fields); you need a template method pattern; you are designing a framework base class. In modern Java (8+), with default methods on interfaces, the line is blurrier โ€” but abstract classes still win when shared mutable state is needed.

โšก Quick Revision Cheat Sheet

PILLAR CHEAT SHEET โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ENCAPSULATION โ†’ private fields + getters/setters INHERITANCE โ†’ extends (IS-A) | single + multilevel + hierarchical POLYMORPHISM โ†’ overloading (compile) + overriding (runtime/DMD) ABSTRACTION โ†’ abstract class (partial) | interface (full contract)
KEYWORD QUICK REFERENCE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ this โ†’ current object ref; this() chains constructor (same class) super โ†’ parent ref; super() chains constructor (parent class) static โ†’ class-level; no instance needed; cannot use this/instance vars final โ†’ variable=constant, method=no override, class=no extend abstract โ†’ class=partial impl, method=no body (must override) instanceof โ†’ type check; pattern matching Java 16+ extends โ†’ class inheritance; interface extension implements โ†’ class โ†’ interface CONSTRUCTOR RULES: โ†’ same name as class; no return type โ†’ default added by compiler ONLY IF no constructor defined โ†’ this()/super() must be FIRST statement; cannot both be in same constructor โ†’ private constructor โ†’ Singleton or utility class OVERRIDING RULES: โ†’ same name + same params + subclass โ†’ cannot override: static, final, private โ†’ covariant return: can narrow return type โœ” โ†’ exception: cannot throw broader checked exceptions โœ” OVERLOADING RULES: โ†’ same name, different params (type/count/order) โ†’ return type alone โ‰  valid overload โ†’ resolution order: exact > widening > autoboxing > varargs
INTERFACE vs ABSTRACT CLASS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ INTERFACE ABSTRACT CLASS Constructor? โŒ โœ… Instance fields? โŒ (only static final) โœ… Multiple inherit? โœ… (implements many) โŒ (extends one) Access modifiers? public only (methods) any Abstract methods? โœ… โœ… Concrete methods? โœ… (default, Java 8) โœ… Private methods? โœ… (Java 9) โœ… static methods? โœ… (Java 8, not inher) โœ… (inherited) When to use: CAN-DO capability IS-A + shared code/state
ACCESS MODIFIERS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ private โ†’ same class only default โ†’ same package protected โ†’ same package + subclasses (any package) public โ†’ everywhere Memory: private < default < protected < public (least to most open)
MEMORY MODEL โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ STACK HEAP โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ method frames all objects (new Foo()) local primitive variables instance variables reference variables arrays auto-freed on return managed by Garbage Collector StackOverflowError OutOfMemoryError โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ String Pool: special heap area for String literals; intern() to add
COLLECTIONS OF ENUMS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ EnumMap โ†’ array-backed, O(1), ordered by declaration โ€” prefer over HashMap EnumSet โ†’ bitmask-backed (1 long for โ‰ค64 constants) โ€” prefer over HashSet
LAMBDA QUICK REFERENCE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Syntax forms: () -> expr no params x -> expr one param (no parens needed) (x, y) -> expr multiple params (x, y) -> { statements; return v; } block body Method reference forms: ClassName::staticMethod static instance::method bound instance ClassName::instanceMethod unbound instance ClassName::new constructor Built-in Functional Interfaces: Predicate boolean test(T t) filter / condition Function R apply(T t) transform Consumer void accept(T t) side-effect Supplier T get() produce value Runnable void run() no params, no return Comparator int compare(T o1, T o2) ordering
CASTING RULES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Upcasting: Animal a = new Dog(); implicit, always safe Downcasting: Dog d = (Dog) a; explicit, risky instanceof: if (a instanceof Dog d) check before cast (Java 16+ pattern) ClassCastException at RUNTIME if actual type is wrong Primitive widening (implicit): byte โ†’ short โ†’ int โ†’ long โ†’ float โ†’ double Narrowing (explicit): double โ†’ (int) d possible data loss
DESIGN PATTERN CHEAT SHEET โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Singleton โ†’ private constructor + static instance Best: enum Singleton { INSTANCE; } Factory Method โ†’ static method returns object; hides constructor Builder โ†’ method chaining (this return); for complex objects Strategy โ†’ interface + multiple implementations; swap at runtime Observer โ†’ Subject notifies all registered Observers (events) Template Method โ†’ abstract class defines skeleton; subclass fills gaps Decorator โ†’ wraps object to add behaviour; same interface

๐Ÿง  Comprehensive MCQ Tricky Points โ€” All Topics

These are the most commonly tested traps across all 30 topics. Each row is a potential MCQ or True/False question.

Classes, Objects & Memory

Statement / QuestionAnswerTrap Explained
A class occupies memory when declaredFALSEOnly objects occupy Heap memory; class is just a blueprint
Two reference variables pointing to same object: change via one affects the other?TRUEBoth refs point to same Heap object โ€” shared state
Java is pass-by-value or pass-by-reference?Always pass-by-valueFor objects, the VALUE of the reference is passed โ€” not the reference itself
Local variables are stored where?StackInstance variables are on Heap (inside the object); local vars on Stack
Default value of an object reference?nullNot 0 or empty โ€” null. Calling method on null โ†’ NullPointerException

Constructors & this/super

Statement / QuestionAnswerTrap Explained
Constructor has a return type of voidFALSEConstructors have NO return type at all โ€” not even void. void makes it a regular method
Compiler adds default constructor when class has a parameterised constructor?FALSECompiler adds default ONLY when NO constructor is defined
Can both this() and super() appear in same constructor?NoBoth must be first statement โ€” contradictory; compiler error
Can a constructor call itself recursively?Nothis() chain must terminate; circular this() chain is a compile error
Is it possible to have a constructor in an interface?NoInterfaces cannot be instantiated; constructors serve no purpose there

Inheritance, Overriding & Polymorphism

Statement / QuestionAnswerTrap Explained
Static methods can be overriddenFALSE โ€” they are hiddenStatic methods are resolved at compile time based on reference type; not polymorphic
Private methods can be overriddenFALSEPrivate methods are invisible to subclasses; same name in subclass = new method, not override
Parent reference to child object: parent ref can call child-specific method?FALSE (compile error)Ref type determines accessible methods at compile time; must downcast
Can overriding method throw a broader checked exception?FALSECan throw narrower or no checked exception; broader = compile error
Method overloading can differ by return type aloneFALSEReturn type alone is NOT enough; parameter list must differ

Static, final & Keywords

Statement / QuestionAnswerTrap Explained
Static block runs every time an object is createdFALSEStatic block runs ONCE when class is first loaded โ€” not per object
A final reference variable cannot have its object's fields changedFALSEfinal ref = reference cannot be reassigned; object's internal fields CAN change
Can a static method use the this keyword?NoNo instance โ†’ no this. Compile error if used in static context
final class can have subclasses?FALSEfinal class cannot be extended. String is final.
abstract class must have at least one abstract methodFALSEAn abstract class can have zero abstract methods โ€” still cannot be instantiated

Interfaces & Abstract Classes

Statement / QuestionAnswerTrap Explained
Interface fields are implicitly public static finalTRUECannot declare private/non-static/non-final fields in an interface
Interface methods are implicitly public abstract (before Java 8)TRUEExplicitly declaring private or protected = compile error (pre-Java 8 for abstract)
A class can implement multiple interfaces and extend a class simultaneouslyTRUEclass Foo extends Bar implements A, B โ€” perfectly valid
default methods in an interface are inherited by sub-interfacesTRUESub-interface inherits default but can re-abstract it or override it
static methods in an interface are inherited by implementing classesFALSECalled as InterfaceName.method() only; not part of implementing class API

Enum, Annotations & Lambda

Statement / QuestionAnswerTrap Explained
Enum can extend another classFALSEAlready extends java.lang.Enum implicitly; single inheritance full
Enum constructor can be publicFALSEMust be private (or package-private); public is a compile error
ordinal() returns 1-based indexFALSE โ€” 0-basedFirst constant ordinal = 0, like array indices
Default @Retention if not specified?CLASSNot SOURCE, not RUNTIME. CLASS = in .class file but not available via reflection
Lambda can capture non-effectively-final local variablesFALSECaptured locals must be effectively final (never reassigned); compile error otherwise
Vararg parameter must be the first parameterFALSE โ€” must be LASTHaving vararg before other params is a compile error
A method can have two vararg parametersFALSEOnly one vararg parameter allowed per method
โญ The 10 Most Commonly Confused Facts โ€” Memorise These
  1. Java is always pass-by-value (even for object references)
  2. Constructor has no return type โ€” not even void
  3. Compiler adds default constructor only if NO constructor is defined
  4. Static methods are hidden in subclasses, not overridden
  5. Abstract class can have zero abstract methods โ€” still cannot be instantiated
  6. Interface static methods are not inherited by implementing classes
  7. Default @Retention is CLASS โ€” not SOURCE, not RUNTIME
  8. Enum ordinal is 0-based; first constant = 0
  9. final reference = reference cannot change; object's fields CAN still change
  10. this() and super() cannot both appear in the same constructor