Theory Questions

Short-answer questions for exam and viva preparation

Unit I: Java Fundamentals

1. Define JDK, JRE, and JVM. Explain their relationship.

JVM (Java Virtual Machine):

  • Abstract machine that provides runtime environment to execute Java bytecode
  • Platform-dependent (different JVM for Windows, Linux, Mac)
  • Performs memory management, garbage collection, security
  • Components: Class Loader, Bytecode Verifier, Execution Engine

JRE (Java Runtime Environment):

  • JVM + Java class libraries + supporting files
  • Provides runtime environment to run Java applications
  • Cannot compile Java programs (no development tools)
  • End users need JRE to run Java applications

JDK (Java Development Kit):

  • JRE + Development tools (javac compiler, debugger, javadoc)
  • Required by developers to write and compile Java programs
  • Includes tools like jar, jdb, javap

Relationship: JDK ⊃ JRE ⊃ JVM

JDK = JRE + Development Tools
JRE = JVM + Library Classes
JVM = Runtime Engine
2. Why is Java called platform-independent? Explain with diagram.

Java achieves platform independence through its "Write Once, Run Anywhere" (WORA) philosophy:

How it works:

  1. Java source code (.java) is compiled by javac compiler
  2. Compiler produces bytecode (.class files), not machine code
  3. Bytecode is platform-independent intermediate code
  4. JVM on any platform interprets/executes this bytecode
  5. JVM converts bytecode to machine-specific instructions
Source Code (.java)
        ↓
   [javac compiler]
        ↓
  Bytecode (.class)  ← Platform Independent
        ↓
   [JVM - Windows]  [JVM - Linux]  [JVM - Mac]
        ↓               ↓              ↓
   Windows Code    Linux Code     Mac Code

Key Points:

  • Java is platform-independent, but JVM is platform-dependent
  • Same bytecode runs on any OS with compatible JVM
  • This differs from C/C++ which compiles directly to machine code
3. Explain the four pillars of OOP with examples.

1. Encapsulation:

Bundling data (variables) and methods that operate on data into a single unit (class), hiding internal state.

class BankAccount {
    private double balance;  // Hidden data

    public void deposit(double amount) {  // Controlled access
        if (amount > 0) balance += amount;
    }

    public double getBalance() {
        return balance;
    }
}

2. Inheritance:

Creating new classes from existing ones, inheriting properties and behaviors. Promotes code reuse.

class Animal {
    void eat() { System.out.println("Eating..."); }
}

class Dog extends Animal {  // Dog inherits from Animal
    void bark() { System.out.println("Barking..."); }
}

// Dog can use eat() from Animal

3. Polymorphism:

One interface, multiple implementations. Same method behaves differently based on object.

// Compile-time (Overloading)
void print(int x) { }
void print(String s) { }

// Runtime (Overriding)
class Shape { void draw() { } }
class Circle extends Shape {
    void draw() { System.out.println("Drawing Circle"); }
}
class Rectangle extends Shape {
    void draw() { System.out.println("Drawing Rectangle"); }
}

Shape s = new Circle();
s.draw();  // "Drawing Circle" - decided at runtime

4. Abstraction:

Hiding complex implementation details, showing only essential features.

abstract class Vehicle {
    abstract void start();  // What to do, not how
}

class Car extends Vehicle {
    void start() {
        // Complex implementation hidden
        System.out.println("Car started");
    }
}
4. Compare and contrast method overloading and method overriding.
Aspect Method Overloading Method Overriding
Definition Same method name, different parameters Same method signature in parent and child
Polymorphism Type Compile-time (Static) Runtime (Dynamic)
Classes Involved Within same class (or inherited) Between parent and child classes
Parameters Must be different Must be same
Return Type Can be different Must be same or covariant
Access Modifier Can be any Cannot be more restrictive
static/private/final Can be overloaded Cannot be overridden
Binding Early binding Late binding

Example of 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; }
}

Example of Overriding:

class Animal {
    void sound() { System.out.println("Some sound"); }
}
class Dog extends Animal {
    @Override
    void sound() { System.out.println("Bark"); }
}
5. Differentiate between abstract class and interface.
Aspect Abstract Class Interface
Methods Abstract and concrete methods Abstract methods (default/static since Java 8)
Variables Any type (instance, static, final) Only public static final (constants)
Constructors Can have constructors Cannot have constructors
Inheritance Single inheritance (extends one) Multiple inheritance (implements many)
Access Modifiers Any access modifier Methods public by default
Keyword extends implements
When to Use Share code among related classes Define a contract for unrelated classes

Example:

// Abstract Class
abstract class Animal {
    String name;  // Instance variable

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

    abstract void sound();  // Abstract method

    void sleep() { System.out.println("Sleeping"); }  // Concrete method
}

// Interface
interface Drawable {
    int MAX_SIZE = 100;  // public static final

    void draw();  // public abstract

    default void print() {  // Default method (Java 8+)
        System.out.println("Printing...");
    }
}
6. Explain all access modifiers in Java with accessibility table.

Java provides four access modifiers to control visibility of classes, methods, and variables:

Modifier Same Class Same Package Subclass (Different Package) Different Package
public
protected
default
private

Usage Guidelines:

  • private: For internal implementation details, helper methods
  • default: For package-level utilities
  • protected: For methods meant to be overridden by subclasses
  • public: For API methods that other classes should use
public class Example {
    public int publicVar;       // Accessible everywhere
    protected int protectedVar; // Same package + subclasses
    int defaultVar;             // Same package only
    private int privateVar;     // This class only
}
7. Explain == vs equals() with examples.

== Operator:

  • Compares references (memory addresses) for objects
  • Compares actual values for primitives
  • Returns true if both references point to same object

equals() Method:

  • Defined in Object class, can be overridden
  • Default implementation uses == (reference comparison)
  • String and wrapper classes override it for content comparison
// Example with Strings
String s1 = new String("Hello");
String s2 = new String("Hello");
String s3 = "Hello";
String s4 = "Hello";

System.out.println(s1 == s2);        // false (different objects)
System.out.println(s1.equals(s2));   // true (same content)
System.out.println(s3 == s4);        // true (String pool - same reference)
System.out.println(s3.equals(s4));   // true (same content)

// Example with primitives
int a = 5, b = 5;
System.out.println(a == b);          // true (value comparison)

// Example with custom class
class Person {
    String name;
    Person(String name) { this.name = name; }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Person) {
            return this.name.equals(((Person) obj).name);
        }
        return false;
    }
}

Person p1 = new Person("John");
Person p2 = new Person("John");
System.out.println(p1 == p2);        // false
System.out.println(p1.equals(p2));   // true (overridden)
8. Explain String pool and String immutability.

String Pool (String Intern Pool):

  • Special memory area in heap for storing String literals
  • When String literal is created, JVM checks pool first
  • If exists, returns existing reference; else creates new entry
  • Saves memory by reusing identical strings
String s1 = "Hello";  // Creates in pool
String s2 = "Hello";  // Returns same reference from pool
String s3 = new String("Hello");  // Creates new object in heap

System.out.println(s1 == s2);  // true (same pool reference)
System.out.println(s1 == s3);  // false (different objects)

// intern() moves string to pool
String s4 = s3.intern();
System.out.println(s1 == s4);  // true

String Immutability:

  • Once created, String content cannot be changed
  • Any modification creates a new String object
  • String class is final (cannot be extended)

Why Strings are Immutable:

  1. Security: Strings used in network connections, database URLs, file paths
  2. String Pool: Multiple references can safely share same String
  3. Thread Safety: Immutable objects are inherently thread-safe
  4. Hashcode Caching: Hash can be cached for HashMap keys
String s = "Hello";
s.concat(" World");  // Creates new String, s unchanged
System.out.println(s);  // "Hello"

s = s.concat(" World");  // s now points to new String
System.out.println(s);  // "Hello World"
9. Compare String, StringBuilder, and StringBuffer.
Feature String StringBuilder StringBuffer
Mutability Immutable Mutable Mutable
Thread Safety Yes (immutable) No Yes (synchronized)
Performance Slow for modifications Fastest Slower than StringBuilder
Memory Creates new objects Modifies in place Modifies in place
Storage String Pool + Heap Heap only Heap only
When to Use Few modifications Single-threaded, many modifications Multi-threaded, many modifications
// String - inefficient for concatenation
String s = "";
for (int i = 0; i < 1000; i++) {
    s += i;  // Creates 1000 new String objects!
}

// StringBuilder - efficient
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);  // Modifies same object
}
String result = sb.toString();

// StringBuffer - thread-safe
StringBuffer sbuf = new StringBuffer();
sbuf.append("Hello");
sbuf.append(" World");
// Safe to use in multiple threads
10. What is a constructor? Explain types and constructor overloading.

Constructor:

  • Special method called automatically when object is created
  • Same name as class, no return type (not even void)
  • Used to initialize object state
  • Cannot be static, final, or abstract

Types of Constructors:

1. Default Constructor:

class Student {
    String name;
    // Compiler provides: Student() { }
}
Student s = new Student();  // Uses default constructor

2. Parameterized Constructor:

class Student {
    String name;
    int age;

    Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
Student s = new Student("John", 20);

3. Copy Constructor (User-defined):

class Student {
    String name;

    Student(Student other) {
        this.name = other.name;
    }
}
Student s1 = new Student("John");
Student s2 = new Student(s1);  // Copy of s1

Constructor Overloading:

class Rectangle {
    int length, width;

    Rectangle() {  // Default
        length = width = 1;
    }

    Rectangle(int side) {  // Square
        length = width = side;
    }

    Rectangle(int l, int w) {  // Custom
        length = l;
        width = w;
    }
}

Rectangle r1 = new Rectangle();        // 1x1
Rectangle r2 = new Rectangle(5);       // 5x5
Rectangle r3 = new Rectangle(4, 6);    // 4x6
11. Explain this and super keywords with examples.

this Keyword:

  • Refers to current object instance
  • Used to distinguish instance variables from parameters
  • Can call current class constructor: this()
  • Can pass current object as argument
class Student {
    String name;
    int age;

    Student(String name, int age) {
        this.name = name;  // this.name = instance variable
        this.age = age;
    }

    Student(String name) {
        this(name, 18);  // Calls parameterized constructor
    }

    void display() {
        print(this);  // Pass current object
    }
}

super Keyword:

  • Refers to immediate parent class object
  • Used to access parent class members (hidden by child)
  • Can call parent class constructor: super()
  • Must be first statement in constructor
class Animal {
    String name = "Animal";

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

    void display() {
        System.out.println("Animal display");
    }
}

class Dog extends Animal {
    String name = "Dog";

    Dog(String name) {
        super(name);  // Call parent constructor
    }

    void display() {
        System.out.println(name);        // Dog
        System.out.println(super.name);  // Animal
        super.display();  // Call parent method
    }
}
12. Explain static keyword - variables, methods, blocks, and nested classes.

Static members belong to class, not instances:

1. Static Variables:

  • One copy shared by all instances
  • Memory allocated when class loads
  • Also called class variables
class Counter {
    static int count = 0;  // Shared
    int id;                // Unique per object

    Counter() {
        count++;
        id = count;
    }
}
Counter c1 = new Counter();  // count=1, c1.id=1
Counter c2 = new Counter();  // count=2, c2.id=2
System.out.println(Counter.count);  // 2

2. Static Methods:

  • Called without creating object
  • Cannot access instance variables/methods directly
  • Cannot use this or super
class MathUtils {
    static int square(int n) {
        return n * n;
    }
}
int result = MathUtils.square(5);  // 25

3. Static Blocks:

  • Executed once when class is loaded
  • Used for static initialization
class Database {
    static Connection conn;

    static {
        System.out.println("Class loading...");
        conn = createConnection();
    }
}

4. Static Nested Classes:

class Outer {
    static class Nested {
        void display() {
            System.out.println("Static nested class");
        }
    }
}
Outer.Nested obj = new Outer.Nested();
13. Explain final keyword with all use cases.

1. Final Variable (Constant):

  • Value cannot be changed once assigned
  • Must be initialized (at declaration, constructor, or static block)
  • Convention: UPPER_CASE names
class Circle {
    final double PI = 3.14159;       // Initialized at declaration
    final int radius;

    Circle(int r) {
        radius = r;                   // Initialized in constructor
    }
}

final int MAX_VALUE = 100;
MAX_VALUE = 200;  // ERROR: cannot reassign

2. Final Method:

  • Cannot be overridden by subclasses
  • Used for security and design constraints
class Parent {
    final void display() {
        System.out.println("Cannot override this");
    }
}

class Child extends Parent {
    // void display() { }  // ERROR: cannot override final method
}

3. Final Class:

  • Cannot be extended (no inheritance)
  • Example: String, Integer, Math classes
final class ImmutableClass {
    // Class content
}

// class ExtendedClass extends ImmutableClass { }  // ERROR

4. Final Parameters:

void process(final int value) {
    // value = 10;  // ERROR: cannot modify final parameter
}

Final vs Immutability:

final List<String> list = new ArrayList<>();
list.add("Hello");    // OK - modifying contents
// list = new ArrayList<>();  // ERROR - reassigning reference
14. Explain wrapper classes, autoboxing, and unboxing.

Wrapper Classes:

Classes that wrap primitive types as objects. Located in java.lang package.

Primitive Wrapper Class Example
byteByteByte.parseByte("10")
shortShortShort.valueOf(100)
intIntegerInteger.parseInt("123")
longLongLong.valueOf(1000L)
floatFloatFloat.parseFloat("3.14")
doubleDoubleDouble.valueOf(3.14)
charCharacterCharacter.valueOf('A')
booleanBooleanBoolean.TRUE

Why Wrapper Classes:

  • Collections only work with objects, not primitives
  • Provide utility methods (parsing, conversion)
  • Allow null values
  • Required for generics

Autoboxing (Primitive → Wrapper):

Integer num = 100;  // Autoboxing: int → Integer
List<Integer> list = new ArrayList<>();
list.add(10);  // Autoboxing

Unboxing (Wrapper → Primitive):

Integer obj = Integer.valueOf(100);
int num = obj;  // Unboxing: Integer → int

int sum = obj + 50;  // Automatic unboxing for arithmetic

Common Methods:

// Parsing strings
int i = Integer.parseInt("123");
double d = Double.parseDouble("3.14");

// Converting to strings
String s = Integer.toString(123);

// Comparing
Integer.compare(10, 20);  // -1

// Min/Max values
Integer.MAX_VALUE;  // 2147483647
Integer.MIN_VALUE;  // -2147483648
15. Explain garbage collection and memory management in Java.

Garbage Collection:

  • Automatic memory management by JVM
  • Reclaims memory from objects no longer in use
  • Runs in background as a daemon thread
  • Cannot be forced, only requested via System.gc()

When an Object Becomes Eligible for GC:

  1. Reference set to null: obj = null;
  2. Reference reassigned: obj = new Object();
  3. Object created inside method (after method returns)
  4. Island of isolation (objects referencing each other, but unreachable)

Memory Areas:

┌─────────────────────────────┐
│          Method Area        │ ← Class metadata, static vars
├─────────────────────────────┤
│            Heap             │ ← Objects, instance variables
│  ┌────────┬───────────────┐ │
│  │ Young  │     Old       │ │
│  │  Gen   │  Generation   │ │
│  └────────┴───────────────┘ │
├─────────────────────────────┤
│     Stack (per thread)      │ ← Local variables, method calls
├─────────────────────────────┤
│     PC Registers            │ ← Current instruction
├─────────────────────────────┤
│     Native Method Stack     │ ← Native method info
└─────────────────────────────┘

finalize() Method:

class Resource {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("Cleanup before GC");
        // Release resources
        super.finalize();
    }
}

Best Practices:

  • Nullify references when done with large objects
  • Use try-with-resources for closeable resources
  • Avoid creating unnecessary objects in loops
  • Use StringBuilder for string concatenation
16. Why is multiple inheritance not supported in Java? How to achieve it?

The Diamond Problem:

If Java allowed multiple inheritance with classes:

class A {
    void display() { System.out.println("A"); }
}
class B extends A {
    void display() { System.out.println("B"); }
}
class C extends A {
    void display() { System.out.println("C"); }
}
class D extends B, C { }  // NOT ALLOWED!
// Which display() would D inherit? Ambiguity!

Solution: Interfaces

Java supports multiple inheritance through interfaces:

interface Printable {
    void print();
}

interface Showable {
    void show();
}

class Document implements Printable, Showable {
    public void print() {
        System.out.println("Printing document");
    }

    public void show() {
        System.out.println("Showing document");
    }
}

Default Methods (Java 8+) Conflict:

interface A {
    default void display() { System.out.println("A"); }
}
interface B {
    default void display() { System.out.println("B"); }
}

class C implements A, B {
    // Must override to resolve conflict
    public void display() {
        A.super.display();  // Choose A's implementation
        // or B.super.display();
        // or provide own implementation
    }
}
17. Explain Java source file structure and naming conventions.

Source File Structure:

// 1. Package declaration (optional, must be first)
package com.example.myapp;

// 2. Import statements (optional)
import java.util.List;
import java.util.ArrayList;
// import java.util.*;  // Wildcard import

// 3. Class/Interface declarations
public class MyClass {
    // Class body
}

class HelperClass {
    // Only one public class per file
}

Naming Conventions:

Element Convention Example
Package lowercase, dot-separated com.company.project
Class/Interface PascalCase (UpperCamelCase) StudentRecord, Runnable
Method camelCase getName(), calculateTotal()
Variable camelCase studentName, totalAmount
Constant UPPER_SNAKE_CASE MAX_VALUE, PI

File Naming Rules:

  • File name must match public class name
  • Extension must be .java
  • One public class per source file
  • Multiple non-public classes allowed
18. Explain data types in Java with memory allocation.

Primitive Data Types:

Type Size Range Default
byte 1 byte -128 to 127 0
short 2 bytes -32,768 to 32,767 0
int 4 bytes -2³¹ to 2³¹-1 0
long 8 bytes -2⁶³ to 2⁶³-1 0L
float 4 bytes ±3.4e38 0.0f
double 8 bytes ±1.7e308 0.0d
char 2 bytes 0 to 65,535 (Unicode) '\u0000'
boolean ~1 bit true / false false

Reference Data Types:

  • Classes, Interfaces, Arrays
  • Store reference (memory address) to actual object
  • Default value is null
  • Objects stored in Heap memory

Type Conversion:

// Implicit (Widening) - Automatic
int i = 100;
long l = i;      // int → long
double d = l;    // long → double

// Explicit (Narrowing) - Casting required
double d = 100.99;
int i = (int) d;  // 100 (decimal lost)

// byte → short → int → long → float → double
19. Explain types of inheritance in Java.

1. Single Inheritance:

class Animal { }
class Dog extends Animal { }
// One parent, one child

2. Multilevel Inheritance:

class Animal { }
class Mammal extends Animal { }
class Dog extends Mammal { }
// Chain of inheritance

3. Hierarchical Inheritance:

class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
// Multiple children, one parent

4. Multiple Inheritance (via Interfaces):

interface Flyable { void fly(); }
interface Swimmable { void swim(); }

class Duck implements Flyable, Swimmable {
    public void fly() { }
    public void swim() { }
}

5. Hybrid Inheritance (via Interfaces):

interface A { }
interface B extends A { }
interface C extends A { }
class D implements B, C { }
// Combination of multiple types

Not Supported: Multiple inheritance with classes (Diamond Problem)

20. What are arrays in Java? Explain with examples.

Array Definition:

  • Container holding fixed number of values of same type
  • Zero-indexed (first element at index 0)
  • Size fixed at creation, cannot be changed
  • Stored in contiguous memory locations

Declaration and Initialization:

// Declaration
int[] numbers;          // Preferred
int numbers[];          // Also valid

// Instantiation
numbers = new int[5];   // Creates array of 5 integers

// Declaration + Instantiation
int[] arr = new int[5];

// Declaration + Initialization
int[] arr = {1, 2, 3, 4, 5};
int[] arr = new int[]{1, 2, 3, 4, 5};

Multi-dimensional Arrays:

// 2D Array
int[][] matrix = new int[3][4];  // 3 rows, 4 columns
int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// Jagged Array (different column sizes)
int[][] jagged = new int[3][];
jagged[0] = new int[2];
jagged[1] = new int[4];
jagged[2] = new int[3];

Common Operations:

int[] arr = {10, 20, 30, 40, 50};

// Access element
int first = arr[0];      // 10

// Length property
int size = arr.length;   // 5

// Iterate
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

// Enhanced for loop
for (int num : arr) {
    System.out.println(num);
}

// Arrays utility class
Arrays.sort(arr);
Arrays.fill(arr, 0);
int index = Arrays.binarySearch(arr, 30);
16. What is the difference between String, StringBuilder, and StringBuffer?

String: Immutable. Every modification creates a new object. Thread-safe by immutability.

StringBuilder: Mutable. More efficient for concatenation. Not thread-safe.

StringBuffer: Mutable and synchronized. Thread-safe but slower than StringBuilder.

Use case: String for fixed values, StringBuilder for single-threaded string manipulation, StringBuffer for multi-threaded scenarios.

17. What is method overloading and what are its rules?

Method overloading: Same method name, different parameters in same class. Compile-time polymorphism.

Rules:

  • Must have different parameter list (number or types)
  • Return type can be different but doesn't contribute to overloading
  • Access modifiers can be different
  • Can throw different exceptions
18. What is method overriding and what are its rules?

Method overriding: Subclass provides specific implementation of parent class method. Runtime polymorphism.

Rules:

  • Must have same signature as parent method
  • Return type must be same or covariant
  • Access modifier cannot be more restrictive
  • Cannot override static, final, or private methods
  • @Override annotation recommended
19. What is the difference between == and equals() method?

== operator: Compares references (memory addresses) for objects. For primitives, compares values.

equals() method: Compares content/values of objects. Defined in Object class, can be overridden.

Example: String s1 = "Hello"; String s2 = new String("Hello");
s1 == s2 returns false (different objects)
s1.equals(s2) returns true (same content)

20. What is the purpose of the finalize() method?

Called by garbage collector before destroying object. Used for cleanup activities (close files, release resources).

Note: Not guaranteed to be called. Deprecated in Java 9. Use try-with-resources or cleaner API instead.

21. What are access modifiers and their scope?
ModifierClassPackageSubclassWorld
publicYesYesYesYes
protectedYesYesYesNo
defaultYesYesNoNo
privateYesNoNoNo
22. What is constructor chaining?

Process of calling one constructor from another constructor using this() or super().

this(): Calls another constructor in same class. Must be first statement.

super(): Calls parent class constructor. Must be first statement.

Use case: Avoid code duplication, initialize object in different ways.

23. What is the difference between break and continue?

break: Exits the loop completely. Control moves to statement after loop.

continue: Skips current iteration. Control moves to next iteration.

Both can be used with labels to break/continue outer loops in nested structures.

24. What is instanceof operator?

Tests whether an object is an instance of a specific class or its subclasses.

Syntax: object instanceof Class

Returns: true if object is instance of Class, false otherwise

Use case: Type checking before casting, avoiding ClassCastException

25. What is the difference between Array and ArrayList?
ArrayArrayList
Fixed sizeDynamic size
Can store primitives and objectsStores only objects (uses wrapper for primitives)
Better performanceSlightly slower due to resizing
length propertysize() method
Cannot add/remove elementsCan add/remove elements

Unit II: Exception Handling, I/O, Multithreading

1. What is an exception? Explain exception hierarchy in Java.

Exception: An event that disrupts the normal flow of program execution. It's an object that represents an error or unexpected condition.

Exception Hierarchy:

java.lang.Object
    └── java.lang.Throwable
            ├── java.lang.Error (Unchecked)
            │       ├── OutOfMemoryError
            │       ├── StackOverflowError
            │       └── VirtualMachineError
            │
            └── java.lang.Exception
                    ├── RuntimeException (Unchecked)
                    │       ├── NullPointerException
                    │       ├── ArrayIndexOutOfBoundsException
                    │       ├── ArithmeticException
                    │       ├── ClassCastException
                    │       └── IllegalArgumentException
                    │
                    └── Checked Exceptions
                            ├── IOException
                            ├── SQLException
                            ├── FileNotFoundException
                            └── ClassNotFoundException

Error vs Exception:

  • Error: Serious problems that application shouldn't try to handle (OutOfMemoryError)
  • Exception: Conditions that application might want to catch and handle
2. Differentiate between checked and unchecked exceptions.
Aspect Checked Exceptions Unchecked Exceptions
Checked at Compile time Runtime
Handling Must handle (try-catch) or declare (throws) Optional to handle
Inheritance Extends Exception (not RuntimeException) Extends RuntimeException
Examples IOException, SQLException, FileNotFoundException NullPointerException, ArithmeticException
Cause External resources (file, network, DB) Programming errors (logic bugs)
Recovery Usually recoverable Usually indicates bugs, fix the code
// Checked Exception - must handle
public void readFile() throws IOException {  // or try-catch
    FileReader fr = new FileReader("file.txt");
}

// Unchecked Exception - optional handling
public void divide(int a, int b) {
    int result = a / b;  // May throw ArithmeticException
}
3. Explain throw vs throws with examples.
Aspect throw throws
Purpose Actually throws an exception Declares exceptions method might throw
Location Inside method body In method signature
Syntax throw new Exception() method() throws Exception
Number Can throw one exception at a time Can declare multiple exceptions
Followed by Exception instance Exception class name(s)
// throw - explicitly throwing exception
public void validateAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("Age cannot be negative");
    }
    if (age < 18) {
        throw new RuntimeException("Must be 18 or older");
    }
}

// throws - declaring exceptions
public void readFile(String filename) throws FileNotFoundException, IOException {
    FileReader fr = new FileReader(filename);
    BufferedReader br = new BufferedReader(fr);
    String line = br.readLine();
}

// Combined usage
public void processFile(String path) throws CustomException {
    if (path == null) {
        throw new CustomException("Path cannot be null");
    }
    // Process file...
}
4. Explain try-catch-finally with all scenarios.

try: Contains code that might throw exceptions

catch: Handles specific exception types

finally: Always executes (cleanup code)

// Basic structure
try {
    // Risky code
    int result = 10 / 0;
} catch (ArithmeticException e) {
    // Handle arithmetic exception
    System.out.println("Cannot divide by zero");
} catch (Exception e) {
    // Handle other exceptions (more general - must come last)
    System.out.println("Error: " + e.getMessage());
} finally {
    // Always executes
    System.out.println("Cleanup code");
}

Multi-catch (Java 7+):

try {
    // Code
} catch (IOException | SQLException e) {
    // Handle both with same code
    System.out.println("Error: " + e.getMessage());
}

Finally Execution Scenarios:

  1. Normal execution: try completes normally → finally runs
  2. Exception caught: exception handled → finally runs
  3. Exception not caught: exception propagates → finally runs first
  4. Return in try: finally runs before return
  5. System.exit(): finally does NOT run
public int testFinally() {
    try {
        return 1;
    } finally {
        System.out.println("Finally runs before return");
        // return 2;  // Would override try's return - bad practice!
    }
}
5. What is try-with-resources? Explain with example.

Automatic resource management introduced in Java 7. Resources are automatically closed after try block.

Requirements:

  • Resource must implement AutoCloseable interface
  • Resources declared in try parentheses
  • Multiple resources separated by semicolons
  • Resources closed in reverse order of declaration
// Traditional way (verbose)
BufferedReader br = null;
try {
    br = new BufferedReader(new FileReader("file.txt"));
    String line = br.readLine();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (br != null) {
        try {
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// Try-with-resources (clean)
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
    String line = br.readLine();
    System.out.println(line);
} catch (IOException e) {
    e.printStackTrace();
}
// br is automatically closed here

// Multiple resources
try (FileInputStream fis = new FileInputStream("input.txt");
     FileOutputStream fos = new FileOutputStream("output.txt")) {
    // Copy file
    int data;
    while ((data = fis.read()) != -1) {
        fos.write(data);
    }
}  // Both streams auto-closed (fos closed first, then fis)

Java 9 Enhancement:

// Resource can be effectively final variable
BufferedReader br = new BufferedReader(new FileReader("file.txt"));
try (br) {
    // Use br
}  // br is closed
6. How to create user-defined exceptions?

Steps to create custom exception:

  1. Extend Exception (checked) or RuntimeException (unchecked)
  2. Provide constructors (typically matching parent constructors)
  3. Add custom fields/methods if needed
// Custom Checked Exception
public class InsufficientFundsException extends Exception {
    private double amount;

    public InsufficientFundsException(String message) {
        super(message);
    }

    public InsufficientFundsException(String message, double amount) {
        super(message);
        this.amount = amount;
    }

    public double getAmount() {
        return amount;
    }
}

// Custom Unchecked Exception
public class InvalidUserException extends RuntimeException {
    public InvalidUserException(String message) {
        super(message);
    }

    public InvalidUserException(String message, Throwable cause) {
        super(message, cause);
    }
}

// Usage
public class BankAccount {
    private double balance;

    public void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException(
                "Cannot withdraw " + amount, amount - balance);
        }
        balance -= amount;
    }
}

// Handling
try {
    account.withdraw(1000);
} catch (InsufficientFundsException e) {
    System.out.println(e.getMessage());
    System.out.println("Shortage: " + e.getAmount());
}
7. Explain byte streams vs character streams.
Aspect Byte Streams Character Streams
Data Unit 8-bit bytes 16-bit Unicode characters
Base Classes InputStream, OutputStream Reader, Writer
Use For Binary data (images, audio, video) Text data
File Classes FileInputStream, FileOutputStream FileReader, FileWriter
Buffered Classes BufferedInputStream, BufferedOutputStream BufferedReader, BufferedWriter
// Byte Stream Example - Reading binary file
try (FileInputStream fis = new FileInputStream("image.jpg");
     FileOutputStream fos = new FileOutputStream("copy.jpg")) {
    byte[] buffer = new byte[1024];
    int bytesRead;
    while ((bytesRead = fis.read(buffer)) != -1) {
        fos.write(buffer, 0, bytesRead);
    }
}

// Character Stream Example - Reading text file
try (BufferedReader br = new BufferedReader(new FileReader("text.txt"));
     BufferedWriter bw = new BufferedWriter(new FileWriter("copy.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
        bw.write(line);
        bw.newLine();
    }
}

Common Stream Classes:

// Byte Streams
FileInputStream / FileOutputStream      // Files
ByteArrayInputStream / ByteArrayOutputStream  // Byte arrays
DataInputStream / DataOutputStream      // Primitive types
ObjectInputStream / ObjectOutputStream  // Objects

// Character Streams
FileReader / FileWriter                 // Files
StringReader / StringWriter             // Strings
BufferedReader / BufferedWriter         // Buffered access
PrintWriter                             // Formatted output
8. What is serialization and deserialization?

Serialization: Converting object state to byte stream for storage or transmission.

Deserialization: Reconstructing object from byte stream.

Requirements:

  • Class must implement java.io.Serializable (marker interface)
  • All instance variables must be serializable or marked transient
  • serialVersionUID recommended for version control
import java.io.*;

class Student implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;
    private int age;
    private transient String password;  // Not serialized

    public Student(String name, int age, String password) {
        this.name = name;
        this.age = age;
        this.password = password;
    }

    @Override
    public String toString() {
        return "Student{name='" + name + "', age=" + age +
               ", password='" + password + "'}";
    }
}

// Serialization
Student student = new Student("John", 20, "secret123");
try (ObjectOutputStream oos = new ObjectOutputStream(
        new FileOutputStream("student.ser"))) {
    oos.writeObject(student);
    System.out.println("Object serialized");
}

// Deserialization
try (ObjectInputStream ois = new ObjectInputStream(
        new FileInputStream("student.ser"))) {
    Student s = (Student) ois.readObject();
    System.out.println("Deserialized: " + s);
    // password will be null (transient)
}

transient Keyword:

  • Fields marked transient are not serialized
  • Use for sensitive data (passwords), calculated fields, or non-serializable objects
9. Differentiate between Process and Thread.
Aspect Process Thread
Definition Independent program in execution Lightweight subprocess within a process
Memory Own memory space (heap, stack) Shares memory with parent process
Communication Inter-process communication (IPC) - complex Direct via shared memory - easy but risky
Overhead Heavy - more resources Light - less resources
Creation Time Slower Faster
Context Switch Expensive Cheaper
Failure Impact One process crash doesn't affect others Thread crash can affect entire process
Example Chrome browser windows Tabs within a browser window
// Process example
ProcessBuilder pb = new ProcessBuilder("notepad.exe");
Process p = pb.start();

// Thread example
Thread t = new Thread(() -> {
    System.out.println("Running in thread");
});
t.start();
10. Explain the complete thread lifecycle with diagram.

Thread States (Thread.State enum):

                    ┌─────────────────┐
                    │      NEW        │
                    │ (Thread created)│
                    └────────┬────────┘
                             │ start()
                             ▼
     ┌──────────────────────────────────────────┐
     │               RUNNABLE                   │
     │  (Ready to run / Running on CPU)         │
     └──────────────────────────────────────────┘
           │                     │           │
           │ wait()              │ sleep(),  │ I/O
           │ join()              │ join(t)   │ blocked
           ▼                     ▼           ▼
    ┌────────────┐      ┌─────────────┐ ┌────────────┐
    │  WAITING   │      │TIMED_WAITING│ │  BLOCKED   │
    │            │      │             │ │(waiting for│
    │(indefinite)│      │(with timeout│ │   lock)    │
    └─────┬──────┘      └──────┬──────┘ └─────┬──────┘
          │ notify()           │ timeout      │ lock
          │ notifyAll()        │ complete     │ acquired
          └────────────────────┴──────────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │   TERMINATED    │
                    │ (run() complete)│
                    └─────────────────┘

State Descriptions:

  • NEW: Thread created but start() not called
  • RUNNABLE: Thread executing or ready to execute
  • BLOCKED: Waiting to acquire a monitor lock
  • WAITING: Waiting indefinitely for another thread (wait(), join())
  • TIMED_WAITING: Waiting for specified time (sleep(), wait(timeout))
  • TERMINATED: Thread execution completed
Thread t = new Thread(() -> {
    System.out.println("Running");
});

System.out.println(t.getState());  // NEW
t.start();
System.out.println(t.getState());  // RUNNABLE
t.join();
System.out.println(t.getState());  // TERMINATED
11. How to create threads in Java? Compare the approaches.

Method 1: Extending Thread class

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread running: " + getName());
    }
}

MyThread t = new MyThread();
t.start();

Method 2: Implementing Runnable interface

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable running");
    }
}

Thread t = new Thread(new MyRunnable());
t.start();

Method 3: Lambda expression (Java 8+)

Thread t = new Thread(() -> {
    System.out.println("Lambda thread");
});
t.start();

Method 4: Implementing Callable (returns value)

Callable<Integer> task = () -> {
    return 42;
};

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(task);
Integer result = future.get();  // 42
executor.shutdown();

Comparison:

Aspect extends Thread implements Runnable
Inheritance Cannot extend another class Can extend another class
Reusability Less reusable More reusable
Thread Pools Cannot use directly Works with ExecutorService
Overhead Each object is a thread Separates task from thread

Recommendation: Use Runnable/Callable with ExecutorService for better resource management.

12. Explain synchronization in Java.

Purpose: Control access to shared resources in multithreaded environment to prevent race conditions.

1. Synchronized Method:

class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;  // Only one thread at a time
    }

    public synchronized int getCount() {
        return count;
    }
}

2. Synchronized Block:

class Counter {
    private int count = 0;
    private Object lock = new Object();

    public void increment() {
        synchronized (lock) {  // Only sync critical section
            count++;
        }
    }

    public void decrement() {
        synchronized (this) {  // Using 'this' as lock
            count--;
        }
    }
}

3. Static Synchronization:

class Counter {
    private static int count = 0;

    public static synchronized void increment() {
        // Lock on Counter.class
        count++;
    }
}

Key Concepts:

  • Monitor/Lock: Every object has an intrinsic lock
  • Mutex: Only one thread can hold the lock at a time
  • Reentrant: Same thread can acquire same lock multiple times

Race Condition Example:

// Without synchronization - WRONG
count++;  // Read-modify-write is not atomic!
// Thread 1: read count (0)
// Thread 2: read count (0)
// Thread 1: write count (1)
// Thread 2: write count (1)  // Lost update!
13. Explain wait(), notify(), and notifyAll() methods.

Inter-thread Communication Methods (Object class):

  • wait(): Releases lock and waits until notified
  • notify(): Wakes up one waiting thread
  • notifyAll(): Wakes up all waiting threads

Rules:

  • Must be called from synchronized context
  • Must own the object's monitor (lock)
  • IllegalMonitorStateException if rules violated
class SharedResource {
    private int data;
    private boolean hasData = false;

    public synchronized void produce(int value) throws InterruptedException {
        while (hasData) {
            wait();  // Wait until consumed
        }
        data = value;
        System.out.println("Produced: " + value);
        hasData = true;
        notify();  // Notify consumer
    }

    public synchronized int consume() throws InterruptedException {
        while (!hasData) {
            wait();  // Wait until produced
        }
        hasData = false;
        notify();  // Notify producer
        System.out.println("Consumed: " + data);
        return data;
    }
}
Aspect wait() sleep()
Class Object Thread
Lock Releases lock Holds lock
Context Must be in synchronized block Can be anywhere
Wake up notify()/notifyAll() or timeout After time elapsed
Purpose Inter-thread communication Pause execution
14. What is deadlock? How to prevent it?

Deadlock: Situation where two or more threads are blocked forever, each waiting for the other's lock.

Conditions for Deadlock:

  1. Mutual Exclusion: Resource can only be held by one thread
  2. Hold and Wait: Thread holds resource while waiting for another
  3. No Preemption: Resources cannot be forcibly taken
  4. Circular Wait: Circular chain of threads waiting for resources

Deadlock Example:

Object lock1 = new Object();
Object lock2 = new Object();

// Thread 1
new Thread(() -> {
    synchronized (lock1) {
        System.out.println("Thread 1: Holding lock1");
        try { Thread.sleep(100); } catch (Exception e) {}

        synchronized (lock2) {  // Waiting for lock2
            System.out.println("Thread 1: Holding lock1 and lock2");
        }
    }
}).start();

// Thread 2
new Thread(() -> {
    synchronized (lock2) {
        System.out.println("Thread 2: Holding lock2");
        try { Thread.sleep(100); } catch (Exception e) {}

        synchronized (lock1) {  // Waiting for lock1 - DEADLOCK!
            System.out.println("Thread 2: Holding lock2 and lock1");
        }
    }
}).start();

Prevention Strategies:

  1. Lock Ordering: Always acquire locks in same order
    // Thread 1 and Thread 2 both do:
    synchronized (lock1) {
        synchronized (lock2) {
            // No deadlock
        }
    }
  2. Lock Timeout: Use tryLock() with timeout
    if (lock.tryLock(1, TimeUnit.SECONDS)) {
        try { /* work */ } finally { lock.unlock(); }
    }
  3. Avoid Nested Locks: Minimize synchronized blocks
  4. Use Higher-level Concurrency: java.util.concurrent utilities
15. What is volatile keyword?

volatile: Ensures variable is always read from and written to main memory, not thread's local cache.

Purpose:

  • Guarantees visibility of changes across threads
  • Prevents compiler optimizations that cache values
  • Establishes happens-before relationship
class VolatileExample {
    private volatile boolean running = true;

    public void stop() {
        running = false;  // Visible to all threads immediately
    }

    public void run() {
        while (running) {
            // Without volatile, thread might cache 'running'
            // and never see the change from stop()
        }
    }
}

volatile vs synchronized:

volatile synchronized
Only visibility guarantee Visibility + atomicity
No blocking Blocking (acquires lock)
For single variables For any code block
Faster Slower

When to use volatile:

  • Simple flags (stop signals)
  • Variables written by one thread, read by many
  • When atomicity not required (single read/write)

When NOT to use volatile:

volatile int count = 0;
count++;  // NOT ATOMIC! Use AtomicInteger or synchronized
16. What is BufferedReader and BufferedWriter?

Purpose: Provide buffering for efficient reading/writing by reducing I/O operations.

How Buffering Works:

  • Data is read/written in chunks to buffer
  • Reduces disk/network access
  • Default buffer size: 8192 characters
// BufferedReader - efficient reading
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {  // Read line by line
        System.out.println(line);
    }
}

// BufferedWriter - efficient writing
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
    bw.write("Hello World");
    bw.newLine();  // Platform-independent newline
    bw.write("Second line");
    bw.flush();  // Ensure all data is written
}

// Reading from console
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.print("Enter name: ");
String name = br.readLine();

Key Methods:

BufferedReader BufferedWriter
read() - single character write(String) - write string
readLine() - entire line newLine() - write line separator
skip(n) - skip characters flush() - flush buffer
ready() - check if ready close() - close stream
17. Explain File class and file operations.

File class: Represents file or directory path. Used for file/directory manipulation (not reading/writing content).

import java.io.File;

// Creating File object
File file = new File("test.txt");
File dir = new File("C:\\Users\\data");
File file2 = new File(dir, "document.txt");

// File Information
System.out.println("Name: " + file.getName());
System.out.println("Path: " + file.getPath());
System.out.println("Absolute: " + file.getAbsolutePath());
System.out.println("Parent: " + file.getParent());
System.out.println("Exists: " + file.exists());
System.out.println("Is File: " + file.isFile());
System.out.println("Is Directory: " + file.isDirectory());
System.out.println("Can Read: " + file.canRead());
System.out.println("Can Write: " + file.canWrite());
System.out.println("Size: " + file.length() + " bytes");
System.out.println("Last Modified: " + new Date(file.lastModified()));

// File Operations
file.createNewFile();         // Create file
file.delete();                // Delete file
file.renameTo(new File("new.txt"));  // Rename

// Directory Operations
dir.mkdir();                  // Create single directory
dir.mkdirs();                 // Create including parent dirs
String[] files = dir.list();  // List file names
File[] fileList = dir.listFiles();  // List File objects

// List with filter
File[] txtFiles = dir.listFiles((d, name) -> name.endsWith(".txt"));

// Delete directory
for (File f : dir.listFiles()) {
    f.delete();
}
dir.delete();
18. What are daemon threads?

Daemon Thread: Background service thread that runs with low priority. JVM terminates when only daemon threads remain.

Characteristics:

  • Runs in background
  • JVM doesn't wait for daemon threads to complete
  • Child of daemon thread is also daemon
  • Used for background tasks (garbage collection, etc.)
// Creating daemon thread
Thread daemonThread = new Thread(() -> {
    while (true) {
        System.out.println("Daemon running...");
        try { Thread.sleep(1000); } catch (Exception e) {}
    }
});

daemonThread.setDaemon(true);  // Must be set before start()
daemonThread.start();

System.out.println("Is daemon: " + daemonThread.isDaemon());

// Main thread does some work
Thread.sleep(3000);
System.out.println("Main ending - daemon will be terminated");
// JVM exits, daemon thread is killed

User Thread vs Daemon Thread:

User Thread Daemon Thread
JVM waits for completion JVM doesn't wait
High priority (foreground) Low priority (background)
Created by user Usually created by JVM
Example: main thread Example: GC thread
19. What is thread priority?

Thread Priority: Hint to thread scheduler about relative importance. Higher priority threads get more CPU time (not guaranteed).

Priority Constants:

  • Thread.MIN_PRIORITY = 1
  • Thread.NORM_PRIORITY = 5 (default)
  • Thread.MAX_PRIORITY = 10
Thread t1 = new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        System.out.println("Low priority: " + i);
    }
});

Thread t2 = new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        System.out.println("High priority: " + i);
    }
});

t1.setPriority(Thread.MIN_PRIORITY);  // 1
t2.setPriority(Thread.MAX_PRIORITY);  // 10

System.out.println("t1 priority: " + t1.getPriority());
System.out.println("t2 priority: " + t2.getPriority());

t1.start();
t2.start();

Important Notes:

  • Priority is only a suggestion to OS scheduler
  • Actual behavior depends on OS and JVM implementation
  • Don't rely on priority for correctness
  • Child thread inherits parent's priority
20. Explain join() method with example.

join(): Makes current thread wait for another thread to complete.

Variants:

  • join() - Wait indefinitely
  • join(long millis) - Wait for milliseconds
  • join(long millis, int nanos) - Wait with nanoseconds
Thread t1 = new Thread(() -> {
    System.out.println("Thread 1 started");
    try { Thread.sleep(2000); } catch (Exception e) {}
    System.out.println("Thread 1 finished");
});

Thread t2 = new Thread(() -> {
    System.out.println("Thread 2 started");
    try { Thread.sleep(1000); } catch (Exception e) {}
    System.out.println("Thread 2 finished");
});

t1.start();
t2.start();

t1.join();  // Main waits for t1
System.out.println("Thread 1 has completed");

t2.join();  // Main waits for t2
System.out.println("Thread 2 has completed");

System.out.println("All threads completed");

// Output:
// Thread 1 started
// Thread 2 started
// Thread 2 finished
// Thread 1 finished
// Thread 1 has completed
// Thread 2 has completed
// All threads completed

Use Cases:

  • Wait for worker threads to complete
  • Ensure sequential execution when needed
  • Collect results from multiple threads

Unit III: Modern Java Features

1. What is a functional interface? Explain with examples.

Functional Interface: An interface with exactly one abstract method. Can have multiple default and static methods.

Characteristics:

  • Contains exactly one abstract method (SAM - Single Abstract Method)
  • Can be annotated with @FunctionalInterface (optional but recommended)
  • Can be implemented using lambda expressions
  • Can have default and static methods
// Custom Functional Interface
@FunctionalInterface
interface Calculator {
    int calculate(int a, int b);  // Single abstract method

    // Default method allowed
    default void print(int result) {
        System.out.println("Result: " + result);
    }

    // Static method allowed
    static int square(int n) {
        return n * n;
    }
}

// Implementation using Lambda
Calculator add = (a, b) -> a + b;
Calculator multiply = (a, b) -> a * b;

System.out.println(add.calculate(10, 5));       // 15
System.out.println(multiply.calculate(10, 5));  // 50
add.print(15);                                  // Result: 15
System.out.println(Calculator.square(4));       // 16

Built-in Functional Interfaces (java.util.function):

Interface Method Description
Predicate<T> test(T t) T → boolean
Function<T,R> apply(T t) T → R
Consumer<T> accept(T t) T → void
Supplier<T> get() () → T
BiFunction<T,U,R> apply(T t, U u) (T, U) → R
UnaryOperator<T> apply(T t) T → T
2. What is a lambda expression? Explain syntax and examples.

Lambda Expression: Anonymous function that provides concise way to implement functional interfaces.

Syntax:

(parameters) -> expression
(parameters) -> { statements; }

// Examples:
() -> 42                           // No parameters
x -> x * 2                         // Single parameter (parentheses optional)
(x, y) -> x + y                    // Multiple parameters
(int x, int y) -> x + y            // Explicit types
(x, y) -> { return x + y; }        // Block with return

Practical Examples:

// Before Lambda
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
};

// With Lambda
Runnable r2 = () -> System.out.println("Hello");

// Comparator
Comparator<String> comp1 = new Comparator<String>() {
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
};

Comparator<String> comp2 = (s1, s2) -> s1.length() - s2.length();

// Using with Collections
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));
names.sort((a, b) -> a.compareTo(b));
names.removeIf(name -> name.length() < 4);

Variable Capture:

int factor = 2;  // Effectively final
Function<Integer, Integer> multiplier = n -> n * factor;
// factor = 3;  // ERROR: must be effectively final
3. Explain method references with all types.

Method Reference: Shorthand notation for lambda expressions that call existing methods. Uses :: operator.

Four Types:

1. Reference to Static Method

// ClassName::staticMethodName
Function<String, Integer> parser = Integer::parseInt;
int num = parser.apply("123");  // 123

// Equivalent lambda
Function<String, Integer> parser2 = s -> Integer.parseInt(s);

2. Reference to Instance Method of Particular Object

// object::instanceMethodName
String str = "Hello World";
Supplier<Integer> lengthSupplier = str::length;
System.out.println(lengthSupplier.get());  // 11

// Equivalent lambda
Supplier<Integer> lengthSupplier2 = () -> str.length();

3. Reference to Instance Method of Arbitrary Object

// ClassName::instanceMethodName
Function<String, String> upper = String::toUpperCase;
System.out.println(upper.apply("hello"));  // HELLO

// Used with streams
List<String> names = Arrays.asList("alice", "bob");
names.stream()
     .map(String::toUpperCase)
     .forEach(System.out::println);

4. Reference to Constructor

// ClassName::new
Supplier<ArrayList<String>> listSupplier = ArrayList::new;
ArrayList<String> list = listSupplier.get();

Function<String, StringBuilder> sbCreator = StringBuilder::new;
StringBuilder sb = sbCreator.apply("Hello");

// Array constructor reference
Function<Integer, String[]> arrayCreator = String[]::new;
String[] arr = arrayCreator.apply(5);  // Creates String[5]
4. Explain Stream API with operations.

Stream: Sequence of elements supporting sequential and parallel aggregate operations. Does not store data, processes on-demand.

Creating Streams:

// From Collection
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream();

// From Array
String[] arr = {"a", "b", "c"};
Stream<String> stream2 = Arrays.stream(arr);

// Using Stream.of()
Stream<String> stream3 = Stream.of("a", "b", "c");

// Infinite Stream
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 1);
Stream<Double> randomStream = Stream.generate(Math::random);

Intermediate Operations (Lazy):

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// filter - select elements matching predicate
numbers.stream().filter(n -> n % 2 == 0);  // Even numbers

// map - transform elements
numbers.stream().map(n -> n * 2);  // Double each

// flatMap - flatten nested structures
List<List<Integer>> nested = Arrays.asList(
    Arrays.asList(1, 2), Arrays.asList(3, 4));
nested.stream().flatMap(List::stream);  // [1, 2, 3, 4]

// distinct - remove duplicates
// sorted - sort elements
// peek - perform action without modifying
// limit - limit to first n elements
// skip - skip first n elements

Terminal Operations:

// forEach - perform action on each element
numbers.stream().forEach(System.out::println);

// collect - collect to collection
List<Integer> result = numbers.stream()
    .filter(n -> n > 5)
    .collect(Collectors.toList());

// reduce - reduce to single value
int sum = numbers.stream().reduce(0, (a, b) -> a + b);

// count, min, max, findFirst, findAny
long count = numbers.stream().count();
Optional<Integer> max = numbers.stream().max(Integer::compare);

// anyMatch, allMatch, noneMatch
boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0);

Complete Example:

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

// Names starting with 'J', uppercase, sorted
List<String> result = names.stream()
    .filter(name -> name.startsWith("J"))
    .map(String::toUpperCase)
    .sorted()
    .collect(Collectors.toList());
// ["JANE", "JOHN"]
5. Explain default and static methods in interfaces.

Default Methods (Java 8+):

  • Methods with implementation in interfaces
  • Use default keyword
  • Allow adding methods without breaking implementations
  • Can be overridden by implementing classes
interface Vehicle {
    void start();  // Abstract method

    default void honk() {  // Default method
        System.out.println("Honking!");
    }

    default void stop() {
        System.out.println("Stopping...");
    }
}

class Car implements Vehicle {
    @Override
    public void start() {
        System.out.println("Car starting");
    }

    // Can use default honk() as-is
    // Can override stop() if needed
    @Override
    public void stop() {
        System.out.println("Car stopping with brake");
    }
}

Static Methods in Interfaces:

interface MathOperations {
    static int add(int a, int b) {
        return a + b;
    }

    static int multiply(int a, int b) {
        return a * b;
    }
}

// Called using interface name
int sum = MathOperations.add(5, 3);  // 8

Diamond Problem Resolution:

interface A {
    default void show() { System.out.println("A"); }
}

interface B {
    default void show() { System.out.println("B"); }
}

class C implements A, B {
    // Must override to resolve conflict
    @Override
    public void show() {
        A.super.show();  // Call A's version
        // or B.super.show();
        // or provide own implementation
    }
}
6. What is Optional class? How to use it?

Optional: Container object that may or may not contain a non-null value. Helps avoid NullPointerException.

Creating Optional:

// Empty Optional
Optional<String> empty = Optional.empty();

// Of non-null value (throws NPE if null)
Optional<String> opt1 = Optional.of("Hello");

// Of nullable value (allows null)
Optional<String> opt2 = Optional.ofNullable(mayBeNull);

Checking and Getting Value:

Optional<String> opt = Optional.of("Hello");

// Check if value present
if (opt.isPresent()) {
    System.out.println(opt.get());
}

// ifPresent with Consumer
opt.ifPresent(value -> System.out.println(value));
opt.ifPresent(System.out::println);

// ifPresentOrElse (Java 9+)
opt.ifPresentOrElse(
    value -> System.out.println("Value: " + value),
    () -> System.out.println("Empty")
);

Getting with Fallback:

Optional<String> opt = Optional.empty();

// orElse - default value
String value1 = opt.orElse("Default");

// orElseGet - supplier (lazy evaluation)
String value2 = opt.orElseGet(() -> computeDefault());

// orElseThrow - throw exception
String value3 = opt.orElseThrow(() -> new RuntimeException("No value"));

// orElseThrow() - Java 10+ throws NoSuchElementException
String value4 = opt.orElseThrow();

Transforming Optional:

Optional<String> opt = Optional.of("hello");

// map - transform value
Optional<Integer> length = opt.map(String::length);  // Optional[5]

// flatMap - when transformation returns Optional
Optional<String> upper = opt.flatMap(s -> Optional.of(s.toUpperCase()));

// filter - keep if predicate matches
Optional<String> filtered = opt.filter(s -> s.length() > 3);

// Chaining
String result = Optional.ofNullable(user)
    .map(User::getAddress)
    .map(Address::getCity)
    .orElse("Unknown");
7. Explain built-in functional interfaces with examples.

1. Predicate<T> - Test condition

Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<String> isEmpty = String::isEmpty;

System.out.println(isEven.test(4));   // true
System.out.println(isEmpty.test("")); // true

// Combining predicates
Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isPositiveEven = isEven.and(isPositive);
Predicate<Integer> isEvenOrPositive = isEven.or(isPositive);
Predicate<Integer> isOdd = isEven.negate();

2. Function<T, R> - Transform

Function<String, Integer> length = String::length;
Function<Integer, Integer> square = n -> n * n;

System.out.println(length.apply("Hello"));  // 5
System.out.println(square.apply(4));        // 16

// Composing functions
Function<String, Integer> composed = length.andThen(square);
System.out.println(composed.apply("Hi"));  // 4 (length 2, squared)

3. Consumer<T> - Consume without return

Consumer<String> printer = System.out::println;
Consumer<List<String>> addItem = list -> list.add("new");

printer.accept("Hello");  // Prints: Hello

// Chaining
Consumer<String> print = s -> System.out.print(s);
Consumer<String> printLn = s -> System.out.println();
Consumer<String> printWithNewLine = print.andThen(printLn);

4. Supplier<T> - Supply value

Supplier<Double> random = Math::random;
Supplier<LocalDate> today = LocalDate::now;
Supplier<List<String>> listFactory = ArrayList::new;

System.out.println(random.get());
System.out.println(today.get());
List<String> list = listFactory.get();

5. BiFunction<T, U, R> - Two inputs

BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
BiFunction<String, String, String> concat = String::concat;

System.out.println(add.apply(5, 3));          // 8
System.out.println(concat.apply("Hi", "!")); // Hi!

6. UnaryOperator<T> - Same type in/out

UnaryOperator<Integer> increment = n -> n + 1;
UnaryOperator<String> toUpper = String::toUpperCase;

System.out.println(increment.apply(5));     // 6
System.out.println(toUpper.apply("hello")); // HELLO
8. What is the difference between intermediate and terminal operations?
Aspect Intermediate Operations Terminal Operations
Return Type Returns Stream Returns non-Stream (void, Optional, Collection, etc.)
Execution Lazy - not executed until terminal called Eager - triggers pipeline execution
Chaining Can be chained Ends the pipeline
Examples filter, map, flatMap, sorted, distinct, peek, limit, skip forEach, collect, reduce, count, min, max, findFirst, anyMatch
List<String> names = Arrays.asList("John", "Jane", "Bob");

// Intermediate operations - just building pipeline
Stream<String> stream = names.stream()
    .filter(n -> n.startsWith("J"))  // Intermediate
    .map(String::toUpperCase)        // Intermediate
    .sorted();                        // Intermediate
// Nothing executed yet!

// Terminal operation - triggers execution
List<String> result = stream.collect(Collectors.toList());
// Now filter, map, sorted all execute

Short-circuiting Operations:

// Intermediate (limit, skip)
Stream.iterate(1, n -> n + 1)
    .limit(5)  // Short-circuit - don't process infinite stream
    .forEach(System.out::println);  // 1, 2, 3, 4, 5

// Terminal (findFirst, findAny, anyMatch, allMatch, noneMatch)
boolean hasJohn = names.stream()
    .anyMatch(n -> n.equals("John"));  // Stops after finding match
9. Explain Collectors in Stream API.

Collectors: Utility class providing reduction operations to collect stream elements.

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

// toList, toSet
List<String> list = names.stream().collect(Collectors.toList());
Set<String> set = names.stream().collect(Collectors.toSet());

// toCollection (specific collection)
TreeSet<String> treeSet = names.stream()
    .collect(Collectors.toCollection(TreeSet::new));

// joining
String joined = names.stream()
    .collect(Collectors.joining(", "));  // "John, Jane, Bob, Jane"
String joinedWith = names.stream()
    .collect(Collectors.joining(", ", "[", "]"));  // "[John, Jane, Bob, Jane]"

// counting
long count = names.stream().collect(Collectors.counting());  // 4

// summarizing (for numeric streams)
IntSummaryStatistics stats = names.stream()
    .collect(Collectors.summarizingInt(String::length));
// count, sum, min, average, max

// groupingBy
Map<Integer, List<String>> byLength = names.stream()
    .collect(Collectors.groupingBy(String::length));
// {3=[Bob], 4=[John, Jane, Jane]}

// partitioningBy (boolean grouping)
Map<Boolean, List<String>> partitioned = names.stream()
    .collect(Collectors.partitioningBy(n -> n.length() > 3));
// {false=[Bob], true=[John, Jane, Jane]}

// toMap
Map<String, Integer> nameToLength = names.stream()
    .distinct()
    .collect(Collectors.toMap(
        name -> name,
        String::length
    ));
10. What are Records in Java?

Record (Java 16+): Compact syntax for immutable data classes. Automatically generates constructor, getters, equals(), hashCode(), toString().

// Traditional class
public class PersonOld {
    private final String name;
    private final int age;

    public PersonOld(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public boolean equals(Object o) { /* ... */ }
    @Override
    public int hashCode() { /* ... */ }
    @Override
    public String toString() { /* ... */ }
}

// Record - single line!
public record Person(String name, int age) { }

// Usage
Person p = new Person("John", 25);
System.out.println(p.name());   // John (accessor, not getName)
System.out.println(p.age());    // 25
System.out.println(p);          // Person[name=John, age=25]

Record Features:

// Custom constructor (compact)
public record Person(String name, int age) {
    public Person {  // Compact constructor - no parameters
        if (age < 0) throw new IllegalArgumentException("Age cannot be negative");
        name = name.toUpperCase();  // Can modify parameters
    }
}

// Additional methods
public record Point(int x, int y) {
    public double distance() {
        return Math.sqrt(x * x + y * y);
    }

    public static Point origin() {
        return new Point(0, 0);
    }
}

// Records can implement interfaces
public record Employee(String name, int id) implements Comparable<Employee> {
    @Override
    public int compareTo(Employee other) {
        return this.name.compareTo(other.name);
    }
}

Limitations:

  • Cannot extend other classes (implicitly extends Record)
  • All fields are final (immutable)
  • Cannot declare instance fields
  • Cannot be abstract
11. Explain var keyword (Local Variable Type Inference).

var (Java 10+): Compiler infers variable type from initialization expression.

// Instead of
ArrayList<String> list1 = new ArrayList<String>();
HashMap<String, List<Integer>> map1 = new HashMap<String, List<Integer>>();

// Use var
var list2 = new ArrayList<String>();
var map2 = new HashMap<String, List<Integer>>();

// Common usages
var name = "John";           // String
var age = 25;                // int
var price = 19.99;           // double
var stream = list.stream();  // Stream<String>

// In loops
for (var item : list) {
    System.out.println(item);
}

for (var i = 0; i < 10; i++) {
    System.out.println(i);
}

Where var can be used:

  • Local variables with initializers
  • For-loop indexes
  • Enhanced for-loop variables
  • Try-with-resources (Java 11+)

Where var cannot be used:

// Method parameters - NO
// void process(var data) { }

// Return type - NO
// var getData() { return "data"; }

// Fields - NO
// class MyClass { var field = 10; }

// Without initializer - NO
// var x;

// With null - NO
// var nothing = null;

// Lambda expressions - NO
// var fn = (x) -> x * 2;  // Cannot infer functional interface

Best Practices:

  • Use when type is obvious from right side
  • Don't sacrifice readability
  • Good for complex generic types
12. What is enhanced switch expression?

Switch Expression (Java 14+): Enhanced switch that can return values and uses arrow syntax.

// Traditional switch (statement)
String dayType;
switch (day) {
    case "MONDAY":
    case "TUESDAY":
    case "WEDNESDAY":
    case "THURSDAY":
    case "FRIDAY":
        dayType = "Weekday";
        break;
    case "SATURDAY":
    case "SUNDAY":
        dayType = "Weekend";
        break;
    default:
        dayType = "Unknown";
}

// Switch expression with arrow syntax
String dayType = switch (day) {
    case "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY" -> "Weekday";
    case "SATURDAY", "SUNDAY" -> "Weekend";
    default -> "Unknown";
};

// With blocks and yield
String result = switch (status) {
    case "ACTIVE" -> {
        log("Processing active user");
        yield "User is active";
    }
    case "INACTIVE" -> {
        log("Processing inactive user");
        yield "User is inactive";
    }
    default -> throw new IllegalArgumentException("Unknown status");
};

Features:

  • No fall-through (no break needed)
  • Multiple case labels separated by comma
  • Can return values (use yield in blocks)
  • Must be exhaustive when returning value

Pattern Matching (Java 17+ Preview):

Object obj = "Hello";

String result = switch (obj) {
    case String s -> "String of length " + s.length();
    case Integer i -> "Integer: " + i;
    case null -> "Null value";
    default -> "Unknown type";
};
13. What are Text Blocks?

Text Blocks (Java 15+): Multi-line string literals using triple quotes.

// Traditional
String html = "<html>\n" +
              "    <body>\n" +
              "        <p>Hello World</p>\n" +
              "    </body>\n" +
              "</html>";

// Text Block
String html = """
              <html>
                  <body>
                      <p>Hello World</p>
                  </body>
              </html>
              """;

// JSON
String json = """
    {
        "name": "John",
        "age": 30,
        "city": "New York"
    }
    """;

// SQL
String sql = """
    SELECT id, name, email
    FROM users
    WHERE status = 'ACTIVE'
    ORDER BY name
    """;

Features:

  • Preserves line breaks
  • No need to escape quotes
  • Automatic indentation management
  • Trailing whitespace stripped by default

Escape Sequences:

// \ at end of line - no newline
String singleLine = """
    This is \
    one line\
    """;  // "This is one line"

// \s - preserve trailing space
String withSpaces = """
    Line with trailing space\s
    Next line
    """;
14. What is pattern matching for instanceof?

Pattern Matching (Java 16+): Combines instanceof check with variable declaration and casting.

// Traditional approach
Object obj = "Hello";
if (obj instanceof String) {
    String s = (String) obj;  // Explicit cast needed
    System.out.println(s.length());
}

// With pattern matching
if (obj instanceof String s) {
    // s is automatically cast to String
    System.out.println(s.length());
}

// In complex conditions
if (obj instanceof String s && s.length() > 5) {
    System.out.println("Long string: " + s);
}

// Negation
if (!(obj instanceof String s)) {
    return;
}
// s is in scope here
System.out.println(s.toUpperCase());

Scope Rules:

// Variable scope limited to where pattern definitely matches
if (obj instanceof String s || obj instanceof Integer i) {
    // s and i NOT in scope - either could match
}

if (obj instanceof String s && s.length() > 0) {
    // s is in scope
}

// In ternary
String result = obj instanceof String s ? s.toUpperCase() : "not string";
15. What are Sealed Classes?

Sealed Classes (Java 17+): Classes that restrict which classes can extend them.

// Sealed class with permits clause
public sealed class Shape permits Circle, Rectangle, Square {
    // Common shape properties
}

// Permitted subclass - must be final, sealed, or non-sealed
public final class Circle extends Shape {
    private double radius;
}

public final class Rectangle extends Shape {
    private double width, height;
}

public sealed class Square extends Shape permits ColoredSquare {
    private double side;
}

// Further subclass of sealed class
public final class ColoredSquare extends Square {
    private String color;
}

// non-sealed - opens up hierarchy again
public non-sealed class Triangle extends Shape {
    // Any class can extend Triangle
}

Benefits:

  • Controlled inheritance hierarchy
  • Exhaustive pattern matching in switch
  • Better maintainability and security
// Exhaustive switch (no default needed)
double area = switch (shape) {
    case Circle c -> Math.PI * c.radius() * c.radius();
    case Rectangle r -> r.width() * r.height();
    case Square s -> s.side() * s.side();
    // Compiler knows all possibilities
};

Unit IV: Collections Framework

1. What is the Collections Framework? Explain its hierarchy.

Collections Framework: A unified architecture for representing and manipulating collections of objects.

Components:

  • Interfaces: Abstract data types (Collection, List, Set, Map)
  • Implementations: Concrete classes (ArrayList, HashSet, HashMap)
  • Algorithms: Static methods for operations (sort, search)
                    Iterable
                        │
                    Collection
          ┌─────────────┼─────────────┐
          │             │             │
        List           Set          Queue
          │             │             │
    ┌─────┼─────┐  ┌────┼────┐   ┌────┼────┐
ArrayList  LinkedList  HashSet  TreeSet  PriorityQueue
Vector                LinkedHashSet       Deque
                         │                   │
                     SortedSet          ArrayDeque
                                        LinkedList

                        Map (separate hierarchy)
          ┌─────────────┼─────────────┐
      HashMap        TreeMap      LinkedHashMap
      Hashtable     SortedMap
   ConcurrentHashMap

Key Interfaces:

  • Collection: Root interface, basic operations (add, remove, size)
  • List: Ordered, allows duplicates, index-based access
  • Set: No duplicates, unordered (except TreeSet)
  • Queue: FIFO ordering, special insertion/removal
  • Map: Key-value pairs, unique keys
2. Compare ArrayList and LinkedList in detail.
Aspect ArrayList LinkedList
Internal Structure Dynamic array Doubly linked list
Random Access O(1) - excellent O(n) - poor
Insert at beginning O(n) - shifts elements O(1) - just update links
Insert at end O(1) amortized O(1)
Insert in middle O(n) - shifts elements O(1) if position known, O(n) to find
Delete O(n) - shifts elements O(1) if position known
Memory Contiguous, less overhead Non-contiguous, more overhead (prev/next pointers)
Cache Locality Good (contiguous memory) Poor (scattered memory)
Implements List, RandomAccess List, Deque, Queue
// ArrayList - good for random access and iteration
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("A");
System.out.println(arrayList.get(0));  // O(1)

// LinkedList - good for frequent insertions/deletions
LinkedList<String> linkedList = new LinkedList<>();
linkedList.addFirst("A");  // O(1)
linkedList.addLast("B");   // O(1)
linkedList.removeFirst();  // O(1)

// LinkedList as Deque/Queue
linkedList.offer("item");   // Add to end
linkedList.poll();          // Remove from front
linkedList.push("item");    // Stack: add to front
linkedList.pop();           // Stack: remove from front

When to Use:

  • ArrayList: Most common choice, frequent reads, rare insertions
  • LinkedList: Frequent add/remove at ends, implementing queue/stack
3. Compare HashSet, LinkedHashSet, and TreeSet.
Aspect HashSet LinkedHashSet TreeSet
Internal Structure Hash table Hash table + Linked list Red-Black tree
Ordering No order Insertion order Sorted order
Performance (add/remove/contains) O(1) O(1) O(log n)
Null Elements One null allowed One null allowed No null (comparison fails)
Memory Less More (linked list overhead) More (tree structure)
When to Use Fast lookup, order not needed Fast lookup + insertion order Sorted data needed
// HashSet - no order
Set<String> hashSet = new HashSet<>();
hashSet.addAll(Arrays.asList("C", "A", "B"));
System.out.println(hashSet);  // [A, B, C] or any order

// LinkedHashSet - insertion order
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.addAll(Arrays.asList("C", "A", "B"));
System.out.println(linkedHashSet);  // [C, A, B]

// TreeSet - sorted order
Set<String> treeSet = new TreeSet<>();
treeSet.addAll(Arrays.asList("C", "A", "B"));
System.out.println(treeSet);  // [A, B, C] - alphabetically sorted

// TreeSet with custom Comparator
TreeSet<String> descending = new TreeSet<>(Comparator.reverseOrder());
descending.addAll(Arrays.asList("C", "A", "B"));
System.out.println(descending);  // [C, B, A]

// TreeSet navigation methods
TreeSet<Integer> nums = new TreeSet<>(Arrays.asList(10, 20, 30, 40, 50));
System.out.println(nums.ceiling(25));  // 30 (smallest >= 25)
System.out.println(nums.floor(25));    // 20 (largest <= 25)
System.out.println(nums.first());      // 10
System.out.println(nums.last());       // 50
4. Compare HashMap, LinkedHashMap, and TreeMap.
Aspect HashMap LinkedHashMap TreeMap
Internal Structure Hash table Hash table + Doubly linked list Red-Black tree
Ordering No order Insertion order (or access order) Sorted by keys
Performance O(1) O(1) O(log n)
Null Keys One null key allowed One null key allowed No null keys
Null Values Multiple allowed Multiple allowed Multiple allowed
Thread-safe No No No
// HashMap
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("B", 2);
hashMap.put("A", 1);
hashMap.put("C", 3);
System.out.println(hashMap);  // Order not guaranteed

// LinkedHashMap - maintains insertion order
Map<String, Integer> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("B", 2);
linkedHashMap.put("A", 1);
linkedHashMap.put("C", 3);
System.out.println(linkedHashMap);  // {B=2, A=1, C=3}

// TreeMap - sorted by keys
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("B", 2);
treeMap.put("A", 1);
treeMap.put("C", 3);
System.out.println(treeMap);  // {A=1, B=2, C=3}

// LRU Cache using LinkedHashMap
LinkedHashMap<String, Integer> lruCache = new LinkedHashMap<>(16, 0.75f, true) {
    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > 3;  // Keep max 3 entries
    }
};
5. Explain Comparable vs Comparator with examples.
Aspect Comparable Comparator
Package java.lang java.util
Method compareTo(Object o) compare(Object o1, Object o2)
Implementation Class itself implements Separate class or lambda
Sort Sequences Single natural ordering Multiple custom orderings
Modification Requires modifying original class No modification needed
Usage Collections.sort(list) Collections.sort(list, comparator)
// Comparable - Natural ordering
class Student implements Comparable<Student> {
    String name;
    int age;
    double gpa;

    @Override
    public int compareTo(Student other) {
        return this.name.compareTo(other.name);  // Natural: by name
    }
}

List<Student> students = new ArrayList<>();
Collections.sort(students);  // Uses compareTo

// Comparator - Custom orderings
Comparator<Student> byAge = (s1, s2) -> Integer.compare(s1.age, s2.age);
Comparator<Student> byGpa = (s1, s2) -> Double.compare(s2.gpa, s1.gpa);  // Descending

Collections.sort(students, byAge);
students.sort(byGpa);

// Comparator utility methods
Comparator<Student> byName = Comparator.comparing(s -> s.name);
Comparator<Student> byAgeDesc = Comparator.comparingInt((Student s) -> s.age).reversed();
Comparator<Student> byNameThenAge = Comparator
    .comparing((Student s) -> s.name)
    .thenComparingInt(s -> s.age);

// Null-safe comparator
Comparator<Student> nullSafe = Comparator.nullsFirst(Comparator.comparing(s -> s.name));
6. Explain Iterator and ListIterator.

Iterator: Universal cursor for traversing any collection.

List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));

// Using Iterator
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if (item.equals("B")) {
        iterator.remove();  // Safe removal during iteration
    }
}
System.out.println(list);  // [A, C, D]

ListIterator: Bidirectional iterator for List implementations.

Iterator ListIterator
Forward traversal only Forward and backward
hasNext(), next() hasPrevious(), previous() also
remove() only add(), set() also
No index access nextIndex(), previousIndex()
Works with any Collection Only List implementations
// Using ListIterator
ListIterator<String> listIterator = list.listIterator();

// Forward
while (listIterator.hasNext()) {
    int index = listIterator.nextIndex();
    String item = listIterator.next();
    System.out.println(index + ": " + item);
}

// Backward
while (listIterator.hasPrevious()) {
    String item = listIterator.previous();
    if (item.equals("A")) {
        listIterator.set("Z");  // Replace current element
    }
}

// Add element at current position
listIterator.add("NEW");

Fail-fast vs Fail-safe:

// Fail-fast (ArrayList, HashMap)
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String item : list) {
    list.add("D");  // ConcurrentModificationException!
}

// Fail-safe (CopyOnWriteArrayList, ConcurrentHashMap)
List<String> safeList = new CopyOnWriteArrayList<>(Arrays.asList("A", "B", "C"));
for (String item : safeList) {
    safeList.add("D");  // Works - iterates over copy
}
7. Explain the internal working of HashMap.

Structure: Array of buckets, each bucket is linked list (or tree for Java 8+).

// Internal structure (simplified)
class HashMap<K, V> {
    Node<K,V>[] table;  // Array of buckets
    int size;
    float loadFactor;   // Default 0.75
    int threshold;      // capacity * loadFactor

    static class Node<K,V> {
        int hash;
        K key;
        V value;
        Node<K,V> next;
    }
}

put() Operation:

  1. Calculate hash: hash = key.hashCode() ^ (hash >>> 16)
  2. Find bucket index: index = hash & (capacity - 1)
  3. If bucket empty, create new node
  4. If key exists (equals check), update value
  5. Else, add to linked list (end) or tree
  6. If size > threshold, resize (double capacity)

get() Operation:

  1. Calculate hash and find bucket
  2. Search bucket (linked list or tree) using equals()
  3. Return value if found, null otherwise
// Hash collision handling
// Java 8+: When bucket has > 8 nodes, converts to tree (O(log n))
// When nodes < 6, converts back to linked list

// Rehashing when capacity doubles
// All entries are re-hashed to new buckets

Performance:

  • Best case: O(1) - no collisions
  • Worst case (before Java 8): O(n) - all in one bucket
  • Worst case (Java 8+): O(log n) - tree bucket
8. Explain hashCode() and equals() contract.

The Contract:

  1. If a.equals(b) == true, then a.hashCode() == b.hashCode()
  2. If a.hashCode() != b.hashCode(), then a.equals(b) == false
  3. If a.hashCode() == b.hashCode(), a.equals(b) may or may not be true (collision)
  4. hashCode() must return same value for same object across invocations
class Person {
    private String name;
    private int age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

// Problems without proper implementation
Map<Person, String> map = new HashMap<>();
Person p1 = new Person("John", 25);
map.put(p1, "Engineer");

Person p2 = new Person("John", 25);  // Same data as p1
String job = map.get(p2);  // null if hashCode/equals not overridden!
                           // "Engineer" if properly overridden

Why Override Both:

  • HashMap uses hashCode() to find bucket
  • Then uses equals() to find exact match in bucket
  • If only equals() overridden: keys with same data go to different buckets
  • If only hashCode() overridden: same hash but never equal
9. Explain List, Set, and Map interfaces.

List Interface:

  • Ordered collection (maintains insertion order)
  • Allows duplicate elements
  • Allows null elements
  • Index-based access (get(i), set(i, e))
  • Implementations: ArrayList, LinkedList, Vector
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("A");     // Duplicate allowed
list.add(1, "C");  // Insert at index
String s = list.get(0);
list.set(0, "Z");
list.remove(0);

Set Interface:

  • No duplicate elements (uses equals())
  • At most one null element
  • No index-based access
  • Implementations: HashSet, LinkedHashSet, TreeSet
Set<String> set = new HashSet<>();
set.add("A");
set.add("B");
set.add("A");  // Ignored - duplicate
boolean exists = set.contains("A");
set.remove("A");

Map Interface:

  • Key-value pairs
  • No duplicate keys (duplicate values allowed)
  • Not part of Collection interface
  • Implementations: HashMap, LinkedHashMap, TreeMap, Hashtable
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("A", 3);  // Updates value for key "A"
int value = map.get("A");  // 3
boolean hasKey = map.containsKey("A");
boolean hasValue = map.containsValue(3);
Set<String> keys = map.keySet();
Collection<Integer> values = map.values();
Set<Map.Entry<String, Integer>> entries = map.entrySet();

// Iterate Map
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}
map.forEach((k, v) -> System.out.println(k + ": " + v));
10. What is ConcurrentHashMap? How is it different from Hashtable?
Aspect Hashtable ConcurrentHashMap
Locking Entire map locked Segment/node-level locking
Concurrency Single thread at a time Multiple threads concurrently
Performance Poor under contention Better scalability
Null Keys/Values Not allowed Not allowed
Iteration Fail-fast Fail-safe (weakly consistent)
Legacy Yes (Java 1.0) No (Java 5)
// ConcurrentHashMap usage
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

// Atomic operations
map.putIfAbsent("key", 1);
map.computeIfAbsent("key", k -> expensiveComputation());
map.computeIfPresent("key", (k, v) -> v + 1);
map.merge("key", 1, Integer::sum);

// Safe iteration
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    // No ConcurrentModificationException
    map.put("newKey", 100);  // Safe
}

// Parallel operations
map.forEach(4, (k, v) -> System.out.println(k + ": " + v));
long count = map.reduceValues(4, v -> 1L, Long::sum);
11. Explain Queue and Deque interfaces.

Queue Interface: FIFO (First-In-First-Out) collection.

Queue<String> queue = new LinkedList<>();

// Throws exception on failure
queue.add("A");     // Add to tail
queue.remove();     // Remove from head
queue.element();    // Peek head

// Returns null/false on failure
queue.offer("A");   // Add to tail
queue.poll();       // Remove from head
queue.peek();       // Peek head

// PriorityQueue - elements sorted by priority
Queue<Integer> pq = new PriorityQueue<>();  // Min-heap
pq.addAll(Arrays.asList(3, 1, 4, 1, 5));
while (!pq.isEmpty()) {
    System.out.println(pq.poll());  // 1, 1, 3, 4, 5
}

// Max-heap
Queue<Integer> maxPq = new PriorityQueue<>(Comparator.reverseOrder());

Deque Interface: Double-ended queue - insert/remove from both ends.

Deque<String> deque = new ArrayDeque<>();

// Add operations
deque.addFirst("A");  // Add to front
deque.addLast("B");   // Add to back
deque.offerFirst("C");
deque.offerLast("D");

// Remove operations
deque.removeFirst();  // Remove from front
deque.removeLast();   // Remove from back
deque.pollFirst();
deque.pollLast();

// Peek operations
deque.getFirst();
deque.getLast();
deque.peekFirst();
deque.peekLast();

// As Stack (LIFO)
deque.push("item");   // addFirst
deque.pop();          // removeFirst
deque.peek();         // peekFirst

// As Queue (FIFO)
deque.offer("item");  // addLast
deque.poll();         // removeFirst
12. How to make collections thread-safe?

1. Using Collections.synchronizedXXX():

// Synchronized collections
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());

// Must synchronize iteration manually
synchronized (syncList) {
    for (String item : syncList) {
        System.out.println(item);
    }
}

2. Using Concurrent Collections:

// Better performance, no external synchronization needed
ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>();
CopyOnWriteArraySet<String> cowSet = new CopyOnWriteArraySet<>();
ConcurrentLinkedQueue<String> concurrentQueue = new ConcurrentLinkedQueue<>();
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(100);

3. Using Immutable Collections:

// Immutable = inherently thread-safe
List<String> immutableList = List.of("A", "B", "C");
Set<String> immutableSet = Set.of("A", "B", "C");
Map<String, Integer> immutableMap = Map.of("A", 1, "B", 2);

// From existing collection
List<String> unmodifiable = Collections.unmodifiableList(existingList);
Approach When to Use
synchronizedXXX() Simple thread-safety, moderate contention
ConcurrentHashMap High concurrency reads/writes
CopyOnWriteArrayList Many reads, few writes
Immutable collections Read-only data, no modification needed
13. What are the differences between HashMap and Hashtable?
Aspect HashMap Hashtable
Thread Safety Not synchronized Synchronized (thread-safe)
Null Keys One null key allowed Not allowed (NullPointerException)
Null Values Multiple nulls allowed Not allowed
Performance Faster (no sync overhead) Slower
Inheritance Extends AbstractMap Extends Dictionary (legacy)
Iterator Fail-fast Fail-fast (Enumerator is fail-safe)
Introduced Java 1.2 Java 1.0 (legacy)
Recommendation Preferred in modern code Use ConcurrentHashMap instead
// HashMap
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put(null, 1);  // OK
hashMap.put("A", null); // OK

// Hashtable
Hashtable<String, Integer> hashtable = new Hashtable<>();
// hashtable.put(null, 1);  // NullPointerException
// hashtable.put("A", null); // NullPointerException

// For thread-safety, use:
Map<String, Integer> safeMap = new ConcurrentHashMap<>();
// or
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
14. What is Vector? How is it different from ArrayList?
Aspect ArrayList Vector
Thread Safety Not synchronized Synchronized
Performance Faster Slower (sync overhead)
Growth 50% increase (n + n/2) 100% increase (2n)
Iteration Iterator (fail-fast) Enumeration + Iterator
Legacy Part of Collections Framework Legacy class (pre-Collections)
Introduced Java 1.2 Java 1.0
// Vector usage (legacy)
Vector<String> vector = new Vector<>();
vector.addElement("A");  // Legacy method
vector.add("B");          // Collections method

// Enumeration (legacy iteration)
Enumeration<String> e = vector.elements();
while (e.hasMoreElements()) {
    System.out.println(e.nextElement());
}

// Modern alternative for thread-safe list
List<String> safeList = Collections.synchronizedList(new ArrayList<>());
// or
List<String> cowList = new CopyOnWriteArrayList<>();

Recommendation: Avoid Vector and Hashtable. Use ArrayList/HashMap with explicit synchronization or concurrent collections.

15. Explain Collections utility class methods.

Collections: Utility class with static methods for collection operations.

List<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5, 9));

// Sorting
Collections.sort(list);                     // Natural order
Collections.sort(list, Collections.reverseOrder());  // Reverse
Collections.sort(list, Comparator.reverseOrder());   // Same

// Searching (list must be sorted)
int index = Collections.binarySearch(list, 4);

// Shuffling
Collections.shuffle(list);
Collections.shuffle(list, new Random(42));  // With seed

// Reversing
Collections.reverse(list);

// Min/Max
int min = Collections.min(list);
int max = Collections.max(list);

// Fill/Copy
Collections.fill(list, 0);  // Fill with 0s
List<Integer> dest = new ArrayList<>(list);
Collections.copy(dest, list);

// Frequency/disjoint
int freq = Collections.frequency(list, 1);  // Count occurrences
boolean disjoint = Collections.disjoint(list1, list2);  // No common elements?

// Rotate/Swap
Collections.rotate(list, 2);     // Rotate elements
Collections.swap(list, 0, 1);    // Swap positions

// Singleton/Empty
List<String> single = Collections.singletonList("only");
List<String> empty = Collections.emptyList();

// Unmodifiable wrappers
List<Integer> unmodifiable = Collections.unmodifiableList(list);

// Synchronized wrappers
List<Integer> syncList = Collections.synchronizedList(list);

// Checked wrapper (type-safe)
List<String> checkedList = Collections.checkedList(new ArrayList<>(), String.class);

Unit V: Spring Framework

1. What is Spring Framework? Explain its key features.

Spring Framework: A comprehensive, modular framework for building enterprise Java applications. It provides infrastructure support and promotes loose coupling.

Key Features:

  • IoC/DI: Dependency Injection and Inversion of Control
  • AOP: Aspect-Oriented Programming for cross-cutting concerns
  • Data Access: Integration with JDBC, ORM (Hibernate, JPA)
  • Transaction Management: Declarative transaction support
  • MVC: Web application framework
  • Security: Spring Security for authentication/authorization
  • Testing: Comprehensive testing support

Spring Modules:

┌─────────────────────────────────────────────────────────┐
│                      Spring Framework                    │
├─────────────┬─────────────┬─────────────┬───────────────┤
│ Spring Core │ Spring AOP  │ Spring Data │ Spring MVC    │
│ (IoC/DI)    │ (Aspects)   │ (DB Access) │ (Web Layer)   │
├─────────────┴─────────────┴─────────────┴───────────────┤
│                   Spring Boot (Simplified Config)        │
└─────────────────────────────────────────────────────────┘

Advantages:

  • Lightweight and non-invasive
  • Promotes POJO-based programming
  • Modular architecture - use what you need
  • Easy testing with dependency injection
  • Rich ecosystem and community support
2. Explain Inversion of Control (IoC) and Dependency Injection (DI).

Inversion of Control (IoC): Design principle where the control of object creation and lifecycle is transferred from the application to a container/framework.

Dependency Injection (DI): A pattern implementing IoC where dependencies are "injected" into objects rather than objects creating their own dependencies.

Without DI (Tight Coupling):

class UserService {
    private UserRepository repository;

    public UserService() {
        // Creating dependency - tight coupling
        this.repository = new MySQLUserRepository();
    }
}
// Problems:
// - Cannot easily switch to PostgreSQL
// - Hard to unit test
// - UserService responsible for creating repository

With DI (Loose Coupling):

class UserService {
    private UserRepository repository;

    // Dependency injected by container
    public UserService(UserRepository repository) {
        this.repository = repository;
    }
}

// In Spring
@Service
public class UserService {
    private final UserRepository repository;

    @Autowired  // Spring injects the dependency
    public UserService(UserRepository repository) {
        this.repository = repository;
    }
}
// Benefits:
// - Easy to switch implementations
// - Easy to test with mocks
// - Loose coupling

IoC Container: Spring provides two main containers:

  • BeanFactory: Basic container, lazy initialization
  • ApplicationContext: Advanced container, eager initialization, AOP, i18n
3. What are the types of Dependency Injection in Spring?

1. Constructor Injection (Recommended):

@Service
public class OrderService {
    private final PaymentService paymentService;
    private final InventoryService inventoryService;

    @Autowired  // Optional for single constructor
    public OrderService(PaymentService paymentService,
                       InventoryService inventoryService) {
        this.paymentService = paymentService;
        this.inventoryService = inventoryService;
    }
}
// Advantages:
// - Dependencies are final (immutable)
// - Required dependencies clearly visible
// - Easy to test, no reflection needed
// - Prevents circular dependencies

2. Setter Injection:

@Service
public class OrderService {
    private PaymentService paymentService;

    @Autowired
    public void setPaymentService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}
// Use case:
// - Optional dependencies
// - Re-configurable dependencies

3. Field Injection (Not Recommended):

@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService;
}
// Disadvantages:
// - Cannot make fields final
// - Hard to test without reflection
// - Hides dependencies
// - Allows many dependencies (code smell)
Type Recommended Use Case
Constructor Yes Required dependencies
Setter Sometimes Optional dependencies
Field No Avoid - only for tests
4. What is Aspect-Oriented Programming (AOP)? Explain its concepts.

AOP: Programming paradigm that allows separating cross-cutting concerns (logging, security, transactions) from business logic.

Key Concepts:

  • Aspect: Module encapsulating cross-cutting concern (e.g., LoggingAspect)
  • Join Point: Point in program execution (method call, exception)
  • Pointcut: Expression selecting join points
  • Advice: Action taken at join point
  • Weaving: Linking aspects with application code
@Aspect
@Component
public class LoggingAspect {

    // Pointcut expression
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}

    // Before advice
    @Before("serviceMethods()")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Calling: " + joinPoint.getSignature().getName());
    }

    // After advice
    @After("serviceMethods()")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("Completed: " + joinPoint.getSignature().getName());
    }

    // Around advice
    @Around("serviceMethods()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();  // Execute method
        long time = System.currentTimeMillis() - start;
        System.out.println("Execution time: " + time + "ms");
        return result;
    }

    // After returning
    @AfterReturning(pointcut = "serviceMethods()", returning = "result")
    public void logResult(JoinPoint joinPoint, Object result) {
        System.out.println("Result: " + result);
    }

    // After throwing
    @AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
    public void logException(JoinPoint joinPoint, Exception ex) {
        System.out.println("Exception: " + ex.getMessage());
    }
}
5. What are the types of Advice in AOP?
Advice Type Annotation When Executed
Before @Before Before method execution
After @After After method (regardless of outcome)
After Returning @AfterReturning After successful completion
After Throwing @AfterThrowing After exception is thrown
Around @Around Wraps method - controls execution

Common Pointcut Expressions:

// All methods in service package
execution(* com.example.service.*.*(..))

// All public methods
execution(public * *(..))

// Methods starting with "get"
execution(* get*(..))

// Methods with specific parameter
execution(* *..UserService.findById(Long))

// Within specific class
within(com.example.service.UserService)

// Bean with specific name
bean(userService)

// Combining expressions
@Pointcut("execution(* com.example.service.*.*(..)) && !execution(* get*(..))")
public void serviceMethodsExceptGetters() {}
6. What are Spring Bean Scopes?
Scope Description When to Use
singleton (default) Single instance per container Stateless services, DAO
prototype New instance for each request Stateful beans
request One per HTTP request Request-specific data
session One per HTTP session User session data
application One per ServletContext Application-wide data
websocket One per WebSocket session WebSocket session data
// Singleton (default)
@Service
public class UserService { }  // Same instance everywhere

// Prototype
@Component
@Scope("prototype")
public class ShoppingCart { }  // New instance each time

// Request scope
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestInfo { }

// Session scope
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserSession { }

// Using @RequestScope and @SessionScope shortcuts
@RequestScope
@Component
public class CurrentRequest { }

@SessionScope
@Component
public class UserPreferences { }
7. What is @Autowired? Explain with @Qualifier.

@Autowired: Tells Spring to automatically inject dependencies. Spring finds matching bean by type.

// Basic usage
@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService;
}

// Constructor injection (recommended)
@Service
public class OrderService {
    private final PaymentService paymentService;

    @Autowired  // Optional for single constructor
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

// Optional dependency
@Autowired(required = false)
private NotificationService notificationService;

@Qualifier: Used when multiple beans of same type exist.

// Multiple implementations
interface PaymentService { }

@Service("creditCard")
class CreditCardPayment implements PaymentService { }

@Service("paypal")
class PayPalPayment implements PaymentService { }

// Injecting specific implementation
@Service
public class OrderService {
    @Autowired
    @Qualifier("creditCard")
    private PaymentService paymentService;
}

// With constructor injection
@Service
public class OrderService {
    private final PaymentService paymentService;

    @Autowired
    public OrderService(@Qualifier("paypal") PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

@Primary: Mark default bean when multiple candidates exist.

@Service
@Primary  // This will be injected by default
class CreditCardPayment implements PaymentService { }
8. What is Spring Boot? How is it different from Spring Framework?

Spring Boot: Framework built on top of Spring to simplify application development with auto-configuration and embedded servers.

Aspect Spring Framework Spring Boot
Configuration Manual (XML or Java config) Auto-configuration
Server External (Tomcat, Jetty) Embedded server
Dependencies Manage individually Starter dependencies
Setup Time Longer Minimal
Deployment WAR to external server JAR with embedded server
Properties Multiple config files application.properties/yml

Spring Boot Features:

// Main application - that's it!
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

// application.properties
server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.jpa.hibernate.ddl-auto=update

// Starter dependencies (pom.xml)
spring-boot-starter-web         // Web application
spring-boot-starter-data-jpa    // JPA/Hibernate
spring-boot-starter-security    // Security
spring-boot-starter-test        // Testing

Spring Boot Auto-configuration:

  • Automatically configures beans based on classpath
  • DataSource if database driver present
  • JPA if spring-data-jpa present
  • Security if spring-security present
  • Can be overridden with custom configuration
9. Explain @Component, @Service, @Repository, @Controller annotations.

These are stereotype annotations that mark classes as Spring-managed components.

// @Component - Generic stereotype
@Component
public class EmailHelper {
    public void sendEmail(String to, String message) { }
}

// @Service - Business logic layer
@Service
public class UserService {
    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User findById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

// @Repository - Data access layer
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    List<User> findByEmail(String email);
}

// @Controller - Web layer (returns views)
@Controller
public class HomeController {
    @GetMapping("/")
    public String home(Model model) {
        model.addAttribute("message", "Welcome");
        return "home";  // Returns view name
    }
}

// @RestController - REST API (returns data)
@RestController
@RequestMapping("/api/users")
public class UserController {
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);  // Returns JSON
    }
}
Annotation Layer Special Behavior
@Component Any Base annotation
@Service Service Semantic clarity
@Repository DAO Exception translation
@Controller Web MVC controller
@RestController REST @Controller + @ResponseBody
10. Explain @RequestMapping and HTTP method annotations.

@RequestMapping: Maps HTTP requests to handler methods.

// Class-level mapping
@RestController
@RequestMapping("/api/users")
public class UserController {

    // GET /api/users
    @RequestMapping(method = RequestMethod.GET)
    public List<User> getAllUsers() { }

    // GET /api/users/123
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public User getUser(@PathVariable Long id) { }

    // POST /api/users
    @RequestMapping(method = RequestMethod.POST)
    public User createUser(@RequestBody User user) { }
}

Shortcut Annotations:

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping                      // GET /api/users
    public List<User> getAll() { }

    @GetMapping("/{id}")            // GET /api/users/{id}
    public User getById(@PathVariable Long id) { }

    @PostMapping                     // POST /api/users
    public User create(@RequestBody User user) { }

    @PutMapping("/{id}")            // PUT /api/users/{id}
    public User update(@PathVariable Long id, @RequestBody User user) { }

    @PatchMapping("/{id}")          // PATCH /api/users/{id}
    public User partialUpdate(@PathVariable Long id, @RequestBody Map<String, Object> updates) { }

    @DeleteMapping("/{id}")         // DELETE /api/users/{id}
    public void delete(@PathVariable Long id) { }
}

Common Attributes:

@GetMapping(
    value = "/search",
    params = "name",                    // Required parameter
    headers = "X-API-VERSION=1",       // Required header
    consumes = "application/json",      // Request content type
    produces = "application/json"       // Response content type
)
public List<User> search(@RequestParam String name) { }
11. Explain the Spring Bean Lifecycle.

Bean Lifecycle Phases:

1. Instantiation        → Bean object created
2. Populate Properties  → Dependencies injected
3. BeanNameAware        → setBeanName() called
4. BeanFactoryAware     → setBeanFactory() called
5. ApplicationContextAware → setApplicationContext() called
6. Pre-Initialization   → BeanPostProcessor.postProcessBeforeInitialization()
7. InitializingBean     → afterPropertiesSet() called
8. Custom init-method   → @PostConstruct or init-method
9. Post-Initialization  → BeanPostProcessor.postProcessAfterInitialization()
10. Bean Ready to Use
11. DisposableBean      → destroy() called
12. Custom destroy      → @PreDestroy or destroy-method

Lifecycle Callbacks:

@Component
public class MyBean implements InitializingBean, DisposableBean {

    // Post-construction (preferred)
    @PostConstruct
    public void init() {
        System.out.println("1. @PostConstruct");
    }

    // InitializingBean interface
    @Override
    public void afterPropertiesSet() {
        System.out.println("2. afterPropertiesSet");
    }

    // Pre-destruction (preferred)
    @PreDestroy
    public void cleanup() {
        System.out.println("3. @PreDestroy");
    }

    // DisposableBean interface
    @Override
    public void destroy() {
        System.out.println("4. destroy");
    }
}

// Configuration-based callbacks
@Bean(initMethod = "init", destroyMethod = "cleanup")
public MyService myService() {
    return new MyService();
}

Recommended Approach:

  • Use @PostConstruct and @PreDestroy (JSR-250)
  • Avoid implementing interfaces (couples to Spring)
  • Constructor injection for required dependencies
12. What is @Transactional annotation?

@Transactional: Declares transaction boundaries declaratively. Spring handles begin, commit, rollback.

@Service
public class AccountService {

    @Transactional
    public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
        Account from = accountRepository.findById(fromId).get();
        Account to = accountRepository.findById(toId).get();

        from.debit(amount);
        to.credit(amount);

        accountRepository.save(from);
        accountRepository.save(to);
        // If any exception occurs, entire transaction rolls back
    }
}

Transaction Attributes:

@Transactional(
    propagation = Propagation.REQUIRED,    // Default
    isolation = Isolation.READ_COMMITTED,   // Default
    timeout = 30,                           // Seconds
    readOnly = false,                       // Optimization hint
    rollbackFor = Exception.class,          // Rollback for
    noRollbackFor = CustomException.class   // Don't rollback for
)
public void process() { }

Propagation Types:

Type Behavior
REQUIRED (default) Join existing or create new
REQUIRES_NEW Always create new, suspend existing
SUPPORTS Join if exists, else non-transactional
NOT_SUPPORTED Execute non-transactionally
MANDATORY Must have existing transaction
NEVER Must not have transaction
NESTED Nested transaction with savepoint
13. Explain ApplicationContext vs BeanFactory.
Aspect BeanFactory ApplicationContext
Bean Loading Lazy (on request) Eager (at startup)
AOP Support No Yes
i18n No Yes (MessageSource)
Event Publishing No Yes
Web Support No Yes (WebApplicationContext)
Resource Loading Basic Rich (ResourceLoader)
Memory Lower Higher
Use Case Simple apps, memory constrained Enterprise applications
// BeanFactory (rarely used directly)
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
MyBean bean = factory.getBean("myBean", MyBean.class);

// ApplicationContext (commonly used)
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// or
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// or in Spring Boot
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(App.class, args);
        MyBean bean = context.getBean(MyBean.class);
    }
}
14. What is @RestController? Explain @PathVariable and @RequestParam.

@RestController: Combines @Controller + @ResponseBody. All methods return data (JSON), not views.

@RestController  // Every method returns data, not view name
@RequestMapping("/api")
public class ApiController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello World";  // Returns as JSON string
    }

    @GetMapping("/user")
    public User getUser() {
        return new User("John", 25);  // Returns as JSON object
    }
}

@PathVariable: Extracts values from URL path.

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

// Multiple path variables: /api/users/123/orders/456
@GetMapping("/users/{userId}/orders/{orderId}")
public Order getOrder(
    @PathVariable Long userId,
    @PathVariable Long orderId) {
    return orderService.findByUserAndOrderId(userId, orderId);
}

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

@RequestParam: Extracts query parameters from URL.

// URL: /api/users?name=John&age=25
@GetMapping("/users")
public List<User> searchUsers(
    @RequestParam String name,
    @RequestParam Integer age) {
    return userService.search(name, age);
}

// Optional parameter with default
@GetMapping("/users")
public List<User> getUsers(
    @RequestParam(required = false, defaultValue = "0") int page,
    @RequestParam(defaultValue = "10") int size) {
    return userService.findAll(page, size);
}

// Multiple values: /api/users?ids=1,2,3
@GetMapping("/users")
public List<User> getUsers(@RequestParam List<Long> ids) {
    return userService.findByIds(ids);
}
15. Explain @RequestBody, @ResponseBody, and ResponseEntity.

@RequestBody: Binds HTTP request body to method parameter. Deserializes JSON/XML to object.

@PostMapping("/users")
public User createUser(@RequestBody User user) {
    // Request body JSON: {"name": "John", "email": "john@email.com"}
    // Automatically converted to User object
    return userService.save(user);
}

// With validation
@PostMapping("/users")
public User createUser(@Valid @RequestBody UserDTO user) {
    return userService.save(user);
}

@ResponseBody: Returns method result as response body (JSON/XML). Included in @RestController.

@Controller  // Regular controller
public class MyController {

    @GetMapping("/user")
    @ResponseBody  // Return JSON instead of view
    public User getUser() {
        return new User("John", 25);
    }
}

ResponseEntity: Full control over HTTP response (status, headers, body).

@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    User user = userService.findById(id);

    if (user == null) {
        return ResponseEntity.notFound().build();  // 404
    }
    return ResponseEntity.ok(user);  // 200 with body
}

@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
    User saved = userService.save(user);
    URI location = URI.create("/api/users/" + saved.getId());

    return ResponseEntity
        .created(location)  // 201 Created
        .header("X-Custom-Header", "value")
        .body(saved);
}

@DeleteMapping("/users/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
    userService.delete(id);
    return ResponseEntity.noContent().build();  // 204 No Content
}

// Error handling
return ResponseEntity
    .status(HttpStatus.BAD_REQUEST)
    .body(new ErrorResponse("Invalid input"));

return ResponseEntity
    .badRequest()
    .body(errors);