0% found this document useful (0 votes)
1 views40 pages

Java Interview Mastery

The 'Java Interview Mastery Guide' is a comprehensive resource for preparing for Java interviews, covering topics from Java 8 to Java 21+ for various skill levels. It includes structured sections with exact interview answers, key points, and code snippets for practical understanding. The guide emphasizes essential Java concepts such as OOP principles, data types, and the differences between JDK, JRE, and JVM, along with practical coding examples.

Uploaded by

dhruvudeniya26
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views40 pages

Java Interview Mastery

The 'Java Interview Mastery Guide' is a comprehensive resource for preparing for Java interviews, covering topics from Java 8 to Java 21+ for various skill levels. It includes structured sections with exact interview answers, key points, and code snippets for practical understanding. The guide emphasizes essential Java concepts such as OOP principles, data types, and the differences between JDK, JRE, and JVM, along with practical coding examples.

Uploaded by

dhruvudeniya26
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

JAVA

INTERVIEW MASTERY GUIDE


Every Topic | Interview Answers | 2-3 Code Snippets Each
Java 8 through Java 21+ | Beginner to Senior Level
How to Use This Guide
This guide is structured for interview preparation. Each topic has 3 parts:

INTERVIEW ANSWER — Speak Exactly Like This


1. INTERVIEW ANSWER — Blue box tells you EXACTLY what to say to an interviewer.
2. KEY POINTS — Green box gives bullet facts to back up your answer.
3. CODE SNIPPETS — 2-3 code examples per subtopic so you can write live on a whiteboard or IDE.

Study tip: Read the blue box aloud every day. Your brain will memorize it naturally.
01. Introduction to Java
1.1 What is Java? — Definition
INTERVIEW ANSWER — Speak Exactly Like This
"Java is a high-level, class-based, object-oriented programming language designed by James Gosling at Sun
Microsystems in 1995.
Its core philosophy is Write Once, Run Anywhere — meaning Java code is compiled into platform-independent
bytecode,
which the Java Virtual Machine (JVM) can execute on any operating system.
Java is strongly typed, garbage-collected, and widely used in enterprise applications, Android development, and
backend systems."

KEY Created by James Gosling at Sun Microsystems, released 1995


KEY WORA = Write Once, Run Anywhere (bytecode runs on any JVM)
lKE Object-Oriented, strongly-typed, platform-independent
Y
KEY Automatic memory management via Garbage Collection
KEY JDK = compiler + tools; JRE = runtime; JVM = bytecode executor

Snippet 1 — The Classic Hello World


// Every Java program starts here

// File must match class name: [Link]


public class Hello {
// Entry point — JVM looks for this exact signature
public static void main(String[] args) {
[Link]("Hello, World!");
}
}

// Compile: javac [Link] → produces [Link] (bytecode)


// Run : java Hello → JVM interprets [Link]

Snippet 2 — How Java Code Flows (Illustrated)


// Java execution pipeline

// Step 1: You write → [Link] (human-readable source)


// Step 2: javac → [Link] (platform-neutral bytecode)
// Step 3: JVM → JIT-compiles bytecode → native machine code

// The JVM is what makes Java cross-platform.


// Different OS = different JVM, but same .class file!

// Check your Java version


// $ java -version
// java version "21.0.1" 2023-10-17 LTS

Snippet 3 — JDK vs JRE vs JVM


// Understanding the Java platform

/*
* JDK (Java Development Kit)
* ├── javac compiler
* ├── java launcher
* ├── jar archive tool
* └── JRE
* ├── Core Libraries ([Link], [Link], [Link] ...)
* └── JVM
* ├── Class Loader
* ├── Bytecode Verifier
* └── Execution Engine (Interpreter + JIT)
*/

// As a developer you need JDK.


// End users only need JRE to run your app.

INTERVIEWER ASKS What is the difference between JDK, JRE, and JVM?

INTERVIEW ANSWER — Speak Exactly Like This


"JDK is the full development kit — it contains the compiler (javac), tools, and the JRE.
JRE is the Java Runtime Environment — it has the core libraries and the JVM, but no compiler.
JVM is the Java Virtual Machine — the engine that actually executes the bytecode.
So when I write code, I need JDK. When someone runs my app, they only need JRE."
02. Data Types & Variables
2.1 Primitive Data Types
INTERVIEW ANSWER — Speak Exactly Like This
"Java has 8 primitive data types that are stored on the stack, not the heap.
The most commonly used are int for whole numbers, double for decimals, boolean for true/false, and char for
single characters.
Each primitive has a fixed size in memory — for example int is always 32 bits.
Primitives are passed by value, meaning a copy is made when passed to a method."

KE 8 primitives: byte, short, int, long, float, double, char, boolean


Y
KE Stored on stack — fast access, no garbage collection needed
Y
KE Default values: int=0, double=0.0, boolean=false, char='\u0000'
Y
KE Wrapper classes (Integer, Double...) box them as objects for collections
Y
KE Passed by VALUE — method gets a copy, original unchanged
Y

Snippet 1 — All 8 Primitive Types


// Primitive types with sizes and ranges

// Integer family
byte b = 100; // 8-bit | -128 to 127
short s = 30_000; // 16-bit | -32,768 to 32,767
int i = 2_000_000; // 32-bit | ~2.1 billion (DEFAULT for
integers)
long l = 9_000_000_000L; // 64-bit | huge range (needs L suffix)

// Decimal family
float f = 3.14f; // 32-bit | 7 decimal digits (needs f suffix)
double d = 3.14159265358; // 64-bit | 15 decimal digits (DEFAULT for
decimals)

// Other
char c = 'A'; // 16-bit Unicode character
boolean ok = true; // true or false — nothing else

// Underscores in literals (Java 7+) — improves readability


int million = 1_000_000;

Snippet 2 — Wrapper Classes & Autoboxing


// Primitives vs Objects

// Autoboxing: primitive → Wrapper object (Java does this automatically)


int primitive = 42;
Integer boxed = primitive; // autoboxing
int unboxed = boxed; // unboxing

// Why wrappers? Collections only hold objects!


List<Integer> list = new ArrayList<>();
[Link](10); // autoboxes int → Integer
int val = [Link](0); // unboxes Integer → int
// Useful wrapper methods
[Link]("42"); // String → int
[Link](10); // "1010"
Integer.MAX_VALUE; // 2147483647
[Link]("3.14"); // String → double

Snippet 3 — Type Casting


// Widening vs Narrowing

// WIDENING (implicit) — smaller → larger, NO data loss, automatic


int x = 100;
long y = x; // int → long (safe, automatic)
double z = x; // int → double (safe, automatic)

// NARROWING (explicit) — larger → smaller, RISK of data loss, must cast


double pi = 3.99;
int pi2 = (int) pi; // pi2 = 3 (decimal part DROPPED, not rounded)

long big = 1_000_000_000_000L;


int cut = (int) big; // WRONG result — overflow silently!

// char ↔ int
char ch = 'A';
int code = ch; // 65 (ASCII/Unicode code point)
char back = (char) 66; // 'B'

CAUTION Narrowing cast truncates (does NOT round). 3.99 becomes 3, not 4. Always mention this in interviews.

2.2 Reference Types & String


INTERVIEW ANSWER — Speak Exactly Like This
"Reference types store an address (reference) on the stack that points to the actual object on the heap.
String is the most commonly used reference type. Strings in Java are immutable —
once created, their content cannot be changed. Every modification creates a new String object.
This is why for heavy string manipulation in loops, we use StringBuilder instead."

Snippet 1 — String Immutability


// Why String is immutable

String s = "Hello";
[Link](" World"); // This creates a NEW string, s is unchanged!
[Link](s); // Still prints: Hello

// Correct way to capture the result


s = [Link](" World");
[Link](s); // Hello World

// String Pool — JVM caches string literals


String a = "Java";
String b = "Java";
[Link](a == b); // true (same pool reference)
String c = new String("Java");
[Link](a == c); // false (c is new heap object)
[Link]([Link](c)); // true (ALWAYS use equals for content!)

Snippet 2 — var (Local Variable Type Inference, Java 10+)


// var keyword
// var lets the compiler infer the type — still STATICALLY typed!
var name = "Alice"; // inferred as String
var count = 42; // inferred as int
var prices = new ArrayList<Double>(); // inferred as ArrayList<Double>

// var is NOT dynamic — the type is fixed at compile time


var x = 10;
// x = "hello"; // COMPILE ERROR — x is int, not String

// var CANNOT be used for:


// - class fields
// - method parameters
// - return types
// - when the right side is null (type is unknown)
03. Object-Oriented Programming (OOP)
3.1 The Four Pillars of OOP
INTERVIEW ANSWER — Speak Exactly Like This
"Java is built on four core OOP pillars:
1. Encapsulation — hiding internal data and exposing only what is needed via getters/setters.
2. Inheritance — a child class acquires properties and behaviour of a parent class using extends.
3. Polymorphism — one interface, many implementations. The same method call behaves differently at runtime.
4. Abstraction — hiding complex implementation details and showing only essential features via abstract
classes and interfaces."

KE Encapsulation = private fields + public getters/setters = data hiding


Y
KE Inheritance = extends keyword, IS-A relationship, code reuse
Y
KE Polymorphism = method overriding (runtime) + overloading (compile-time)
Y
KE Abstraction = abstract class or interface hides 'how', exposes 'what'
Y
KE Java supports single class inheritance but multiple interface implementation
Y

3.2 Classes & Objects


INTERVIEW ANSWER — Speak Exactly Like This
"A class is a blueprint or template. An object is a real instance created from that blueprint.
When I write new Person(), the JVM allocates memory on the heap and calls the constructor to initialise the
object.
Every object has state (fields) and behaviour (methods)."

Snippet 1 — Well-structured Class


// Proper class design with encapsulation

public class BankAccount {


// Private fields — nobody can touch these directly
private String owner;
private double balance;
private static int totalAccounts = 0; // shared across all instances

// Constructor
public BankAccount(String owner, double initialBalance) {
if (initialBalance < 0) throw new IllegalArgumentException("Balance
cannot be negative");
[Link] = owner;
[Link] = initialBalance;
totalAccounts++;
}

// Getter
public double getBalance() { return balance; }
public String getOwner() { return owner; }

// Business method
public void deposit(double amount) {
if (amount <= 0) throw new IllegalArgumentException("Deposit must be
positive");
balance += amount;
}

public boolean withdraw(double amount) {


if (amount > balance) return false;
balance -= amount;
return true;
}

public static int getTotalAccounts() { return totalAccounts; }

@Override
public String toString() {
return [Link]("BankAccount[owner=%s, balance=%.2f]", owner,
balance);
}
}

Snippet 2 — Creating & Using Objects


// Object instantiation and usage

BankAccount acc1 = new BankAccount("Alice", 1000.0);


BankAccount acc2 = new BankAccount("Bob", 500.0);

[Link](250.0);
boolean success = [Link](100.0);
[Link](acc1); // BankAccount[owner=Alice,
balance=1150.00]
[Link](success); // true

// Static member accessed on class, not object


[Link]([Link]()); // 2

// Both variables point to different objects on the heap


[Link](acc1 == acc2); // false — different references

Snippet 3 — Constructor Overloading with this()


// Multiple constructors calling each other

public class Rectangle {


double width, height;

Rectangle() { this(1.0, 1.0); } // default


square
Rectangle(double side) { this(side, side); } // square
Rectangle(double width, double height) { // main
constructor
[Link] = width;
[Link] = height;
}

double area() { return width * height; }


double perimeter() { return 2 * (width + height); }
}

Rectangle r1 = new Rectangle(); // 1x1


Rectangle r2 = new Rectangle(5); // 5x5
Rectangle r3 = new Rectangle(4, 6); // 4x6
[Link]([Link]()); // 24.0
3.3 Inheritance
INTERVIEW ANSWER — Speak Exactly Like This
"Inheritance allows a subclass to inherit fields and methods from a superclass using the extends keyword.
This promotes code reuse. The subclass can override methods to provide its own implementation.
In Java, every class implicitly extends Object, which is the root of the entire class hierarchy.
Java supports single inheritance for classes, but a class can implement multiple interfaces."

KE extends keyword creates IS-A relationship


Y
KE super() calls parent constructor — MUST be first line in child constructor
Y
KE @Override annotation ensures we are actually overriding, not overloading
Y
KE final class cannot be extended; final method cannot be overridden
Y
KE Java: single class inheritance, multiple interface inheritance
Y

Snippet 1 — Inheritance Chain


// Parent → Child → Grandchild

// Parent
public class Vehicle {
String brand;
int speed;
Vehicle(String brand, int speed) { [Link]=brand; [Link]=speed; }
public void move() { [Link](brand + " moves at " + speed +
" km/h"); }
public String info(){ return brand + " (speed=" + speed + ")"; }
}

// Child — adds fuel type, overrides info()


public class Car extends Vehicle {
String fuelType;
Car(String brand, int speed, String fuelType) {
super(brand, speed); // calls Vehicle constructor
[Link] = fuelType;
}
@Override public String info() { return [Link]() + ", fuel=" +
fuelType; }
public void honk() { [Link](brand + ":
Beep!"); }
}

// Grandchild
public class ElectricCar extends Car {
int batteryKWh;
ElectricCar(String brand, int speed, int kwh) {
super(brand, speed, "Electric");
[Link] = kwh;
}
@Override public String info() { return [Link]() + ", battery=" +
batteryKWh + "kWh"; }
}

ElectricCar tesla = new ElectricCar("Tesla", 250, 100);


[Link](); // inherited from Vehicle
[Link](); // inherited from Car
[Link]([Link]()); // Tesla (speed=250), fuel=Electric,
battery=100kWh

Snippet 2 — Method Overriding Rules


// Rules an interviewer will test

class Animal {
// Covariant return type: subclass can return narrower type
public Animal create() { return new Animal(); }

// Access: subclass CAN make it more accessible, NOT less


protected void sound() { [Link]("..."); }
}

class Dog extends Animal {


@Override
public Dog create() { return new Dog(); } // OK: Dog is subtype of
Animal

@Override
public void sound() { [Link]("Woof"); } // OK: public >
protected

// @Override
// private void sound() { } // COMPILE ERROR: more restrictive access
}

3.4 Polymorphism
INTERVIEW ANSWER — Speak Exactly Like This
"Polymorphism means many forms. In Java it has two types:
1. Compile-time (static) polymorphism — method overloading. Same method name, different parameters.
2. Runtime (dynamic) polymorphism — method overriding. The JVM decides which version to call based on the
actual object type at runtime, not the reference type.
This is also called dynamic method dispatch and is the foundation of the Strategy and Template Method design
patterns."

Snippet 1 — Runtime Polymorphism (Dynamic Dispatch)


// One reference type, many behaviours

// Base reference, different actual objects


Animal[] zoo = {
new Dog("Rex"),
new Cat("Mimi"),
new Parrot("Polly")
};

for (Animal a : zoo) {


[Link](); // JVM calls the ACTUAL object's sound() at runtime
}
// Output:
// Woof
// Meow
// Squawk

// The reference type is Animal, but the behaviour is of Dog/Cat/Parrot


// This is DYNAMIC METHOD DISPATCH
Snippet 2 — Method Overloading (Compile-time)
// Same name, different signatures

public class Calculator {


// Overloaded methods — different parameter types/count
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; }
String add(String a, String b) { return a + b; }
}

Calculator c = new Calculator();


[Link]([Link](1, 2)); // 3 — calls int version
[Link]([Link](1.5, 2.5)); // 4.0 — calls double version
[Link]([Link]("Hi", " there")); // Hi there — calls String
version

// Note: return type alone does NOT distinguish overloads!

Snippet 3 — instanceof & Pattern Matching


// Checking and casting safely

Animal a = new Dog("Buddy");

// Old style (pre Java 16)


if (a instanceof Dog) {
Dog d = (Dog) a;
[Link]();
}

// Pattern Matching (Java 16+) — cleaner, type-safe


if (a instanceof Dog d) {
[Link](); // d is already cast, no explicit cast needed
}

// In switch (Java 21+)


String desc = switch (a) {
case Dog d -> [Link]() + " is a dog";
case Cat c -> [Link]() + " is a cat";
default -> "Unknown animal";
};

3.5 Abstraction — Abstract Classes & Interfaces


INTERVIEW ANSWER — Speak Exactly Like This
"Abstraction hides implementation complexity and shows only the essential features.
In Java we achieve abstraction in two ways:
Abstract classes — can have both abstract methods (no body) and concrete methods.
Interfaces — define a pure contract. From Java 8 onwards, interfaces can have default and static methods.
The key rule: if a class extends an abstract class or implements an interface,
it MUST provide implementations for all abstract methods, or itself be declared abstract."

KE abstract class: 0-100% abstraction, can have constructors and state


Y
KE interface: 100% abstraction by default (before Java 8)
Y
KE Interface methods are implicitly public abstract unless default/static
Y
KE A class can extend only ONE abstract class but MANY interfaces
Y
KE abstract class used when subclasses share common code; interface for capability/contract
Y

Snippet 1 — Abstract Class


// Template Method Pattern via abstract class

public abstract class DataProcessor {


// Template method — defines algorithm skeleton
public final void process() {
readData(); // abstract — subclass decides
processData(); // abstract — subclass decides
writeOutput(); // concrete — same for everyone
}

protected abstract void readData();


protected abstract void processData();

protected void writeOutput() { // concrete with default behaviour


[Link]("Writing output...");
}
}

public class CsvProcessor extends DataProcessor {


@Override protected void readData() { [Link]("Reading
CSV..."); }
@Override protected void processData() { [Link]("Processing
CSV..."); }
}

new CsvProcessor().process();
// Reading CSV...
// Processing CSV...
// Writing output...

Snippet 2 — Interface with Default Methods (Java 8+)


// Interface as a contract + default behaviour

public interface Flyable {


void fly(); // abstract
default void land() { // default — subclass can
override
[Link]("Landing...");
}
static Flyable noOp() { return () -> {}; } // static factory
}

public interface Swimmable {


void swim();
default void float_() { [Link]("Floating..."); }
}

// Multiple interface implementation


public class Duck implements Flyable, Swimmable {
@Override public void fly() { [Link]("Duck flies!"); }
@Override public void swim() { [Link]("Duck swims!"); }
}

Duck d = new Duck();


[Link](); [Link](); [Link](); d.float_();

Snippet 3 — Abstract Class vs Interface Comparison


// When to use which — interview favourite!

// Use ABSTRACT CLASS when:


// - You want to share code (fields, constructors, concrete methods)
// - IS-A relationship: Dog IS-A Animal
abstract class Animal {
String name; // shared state — can't do this in interface
Animal(String name) { [Link] = name; }
abstract void sound();
void breathe() { [Link](name + " breathes"); }
}

// Use INTERFACE when:


// - Defining a capability / contract: CAN-DO relationship
// - Multiple inheritance of type needed
interface Serializable { void serialize(); }
interface Printable { void print(); }

class Report extends Document // one abstract class


implements Serializable, // many interfaces
Printable {
@Override public void serialize() { /* ... */ }
@Override public void print() { /* ... */ }
}
04. Strings In Depth
4.1 String Methods — Most Asked
INTERVIEW ANSWER — Speak Exactly Like This
"String in Java is a final, immutable class in [Link] package.
Being immutable means once a String object is created, its value cannot be changed.
This makes Strings thread-safe and suitable for use as HashMap keys.
Java maintains a String Pool in the heap where literal strings are cached to save memory."

Snippet 1 — Essential String Methods


// Methods every developer must know

String s = " Hello, Java World! ";

// Length & access


[Link](); // 22
[Link](7); // 'J'
[Link]('o'); // 4 (first occurrence)
[Link]('o'); // 12

// Trimming
[Link](); // "Hello, Java World!" (removes spaces)
[Link](); // Java 11+ (Unicode-aware, preferred)
[Link](); // "Hello, Java World! "
[Link](); // " Hello, Java World!"

// Substrings
[Link]().substring(7); // "Java World!"
[Link]().substring(7, 11); // "Java"

// Search & Replace


[Link]("Java"); // true
[Link](" Hello"); // true
[Link]("! "); // true
[Link]("Java", "Python"); // " Hello, Python World! "
[Link]("\\s+", "-"); // replace all whitespace with -

// Split & Join


"a,b,c".split(","); // ["a", "b", "c"]
[Link]("-", "a","b","c"); // "a-b-c"

// Check
[Link](); // false (has chars)
" ".isBlank(); // true (Java 11+, only whitespace)

// Case
[Link]();
[Link]();

Snippet 2 — String Comparison (Most Common Bug)


// == vs equals vs compareTo

String a = "hello";
String b = "hello";
String c = new String("hello");

// == compares REFERENCES (memory addresses)


[Link](a == b); // true (both from string pool)
[Link](a == c); // FALSE (c is new object on heap)

// equals() compares CONTENT — ALWAYS use this!


[Link]([Link](c)); // true
[Link]([Link]("HELLO")); // true

// compareTo() — for sorting (lexicographic)


"apple".compareTo("banana"); // negative (apple < banana)
"banana".compareTo("apple"); // positive (banana > apple)
"apple".compareTo("apple"); // 0 (equal)

Snippet 3 — StringBuilder vs String


// Performance-critical string building

// BAD — String concatenation in loop creates n new String objects = O(n^2)


String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // AVOID in loops!
}

// GOOD — StringBuilder modifies internal buffer = O(n)


StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
[Link](i);
}
String result2 = [Link]();

// StringBuilder key methods


StringBuilder s = new StringBuilder("Hello");
[Link](" World"); // Hello World
[Link](5, ","); // Hello, World
[Link](5, 6); // Hello World
[Link](6, 11, "Java"); // Hello Java
[Link](); // avaJ olleH
[Link](); // current length
[Link](); // convert to String

CAUTION StringBuffer is thread-safe but slower than StringBuilder. Use StringBuilder in single-threaded code
(which is most cases).
05. Collections Framework
5.1 Collection Hierarchy Overview
INTERVIEW ANSWER — Speak Exactly Like This
"The Java Collections Framework provides ready-to-use data structures.
At the top is the Collection interface with three main sub-interfaces:
List — ordered, allows duplicates (ArrayList, LinkedList).
Set — no duplicates (HashSet, LinkedHashSet, TreeSet).
Queue — FIFO ordering (LinkedList, PriorityQueue).
Map is separate — it holds key-value pairs. HashMap, LinkedHashMap, TreeMap.
The choice of collection depends on whether you need ordering, uniqueness, or fast key-based access."

Snippet 1 — Collection Hierarchy (as code comment)


// Visual hierarchy

/*
Iterable
└── Collection
├── List → ArrayList, LinkedList, Vector
├── Set → HashSet, LinkedHashSet, TreeSet
└── Queue → LinkedList, ArrayDeque, PriorityQueue
Map (NOT a Collection!)
→ HashMap, LinkedHashMap, TreeMap, Hashtable

Choosing the right collection:


Need fast random access? → ArrayList
Need fast insert/delete at ends? → LinkedList / ArrayDeque
Need uniqueness? → HashSet (unordered)
Need uniqueness + order? → TreeSet (sorted) / LinkedHashSet
(insertion)
Need key-value lookup? → HashMap (O(1) average)
Need sorted keys? → TreeMap (O(log n))
*/

5.2 ArrayList — Most Used


INTERVIEW ANSWER — Speak Exactly Like This
"ArrayList is a resizable array. It is backed by a plain array internally.
When the array is full, it creates a new array of 1.5x the size and copies elements — this is called resizing.
ArrayList gives O(1) access by index, O(n) for insert/delete in the middle.
It is NOT thread-safe. Use [Link]() or CopyOnWriteArrayList for concurrency."

Snippet 1 — ArrayList Operations


// All important ArrayList operations

import [Link].*;

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

// Adding
[Link]("Alice");
[Link]("Bob");
[Link](0, "Zara"); // insert at index 0 — shifts others right
[Link]([Link]("Dan", "Eve"));

// Accessing
[Link](0); // "Zara"
[Link](); // 5
[Link]("Alice"); // true
[Link]("Bob"); // 2

// Modifying
[Link](1, "Anna"); // replace at index 1
[Link]("Bob"); // remove by value
[Link](0); // remove by index

// Iterating
[Link]([Link]::println);
for (String s : list) { [Link](s); }

// Sorting
[Link](list);
[Link]([Link]());
[Link]([Link](String::length));

// Convert to array
String[] arr = [Link](new String[0]);

// Immutable list (Java 9+) — fixed size, no add/remove


List<String> fixed = [Link]("a", "b", "c");

Snippet 2 — ArrayList vs LinkedList


// When to use which

// ArrayList — backed by array


List<Integer> arrayList = new ArrayList<>();
// O(1) get(index) — direct array access
// O(n) add(index, e) — must shift elements
// Best for: read-heavy workloads, random access

// LinkedList — doubly-linked list


List<Integer> linkedList = new LinkedList<>();
// O(n) get(index) — must traverse nodes
// O(1) add/remove at ends (as Deque)
// Best for: frequent insert/delete at head/tail

Deque<Integer> deque = new LinkedList<>();


[Link](1); // stack-like: push to front
[Link](2); // queue-like: enqueue at back
[Link](); // look at front without removing
[Link](); // remove from front

5.3 HashMap — Most Asked


INTERVIEW ANSWER — Speak Exactly Like This
"HashMap stores key-value pairs. Internally it uses an array of buckets.
The key is hashed using hashCode(), then mapped to a bucket index.
If two keys hash to the same bucket — called a collision — they are stored as a linked list (or tree in Java 8+
when size > 8).
HashMap gives O(1) average time for get and put, but worst case O(n) with many collisions.
It allows one null key and multiple null values. It is NOT thread-safe."

KE Internally: array of buckets + LinkedList/TreeNode (Java 8+) for collisions


Y
KE Requires proper hashCode() AND equals() on key objects
Y
KE Default load factor 0.75 — rehashes when 75% full
Y
KE Iteration order NOT guaranteed — use LinkedHashMap for insertion order
Y
KE ConcurrentHashMap for thread-safe operations
Y

Snippet 1 — HashMap Complete Operations


// All essential HashMap operations

Map<String, Integer> scores = new HashMap<>();

// Put
[Link]("Alice", 95);
[Link]("Bob", 87);
[Link]("Alice", 99); // replaces 95 with 99

// Get
int a = [Link]("Alice"); // 99
int x = [Link]("Zara", 0);// 0 — key not found, return default

// Check
[Link]("Bob"); // true
[Link](99); // true
[Link](); // 2

// Safe insert (only if key absent)


[Link]("Charlie", 70);

// Compute
[Link]("Alice", 1, Integer::sum); // Alice = 99+1 = 100
[Link]("Bob", (k, v) -> v == null ? 0 : v + 5); // Bob = 92
[Link]("Dave", k -> 80); // Dave = 80 (only if absent)

// Remove
[Link]("Bob");

// Iterate
for ([Link]<String,Integer> e : [Link]()) {
[Link]([Link]() + " -> " + [Link]());
}
[Link]((k, v) -> [Link](k + "=" + v));

// Immutable map (Java 9+)


Map<String,Integer> fixed = [Link]("x", 1, "y", 2);

Snippet 2 — hashCode & equals Contract


// Critical for using custom objects as keys

// If you use a custom class as a Map key,


// you MUST override both hashCode() and equals()
public class Student {
int id;
String name;

Student(int id, String name) { [Link]=id; [Link]=name; }


@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student s)) return false;
return id == [Link] && [Link]([Link]);
}

@Override
public int hashCode() {
return [Link](id, name);
}
}

// Rule: if [Link](b) then [Link]() == [Link]()


// Violation causes: same key added twice, get() never finds the key!

5.4 Set Implementations


Snippet 1 — HashSet, LinkedHashSet, TreeSet
// Three Set implementations compared

// HashSet — no order, O(1) add/contains


Set<String> hs = new HashSet<>([Link]("banana","apple","cherry","apple"));
[Link](hs); // [banana, cherry, apple] — NO duplicates,
random order

// LinkedHashSet — insertion order maintained


Set<String> lhs = new
LinkedHashSet<>([Link]("banana","apple","cherry","apple"));
[Link](lhs); // [banana, apple, cherry] — insertion order

// TreeSet — always sorted, O(log n)


Set<String> ts = new TreeSet<>([Link]("banana","apple","cherry"));
[Link](ts); // [apple, banana, cherry] — sorted!

// Custom sort
Set<String> byLength = new TreeSet<>([Link](String::length)
.thenComparing([Link]
ralOrder()));
[Link]([Link]("fig", "kiwi", "apple", "pear"));
[Link](byLength); // [fig, kiwi, pear, apple]
06. Exception Handling
6.1 Exception Hierarchy & Types
INTERVIEW ANSWER — Speak Exactly Like This
"In Java, all exceptions and errors extend Throwable.
There are two types of exceptions:
Checked exceptions — like IOException, SQLException. The compiler FORCES you to handle them with try-
catch or declare them with throws.
Unchecked exceptions — RuntimeException and its subclasses like NullPointerException,
ArrayIndexOutOfBoundsException. These are programmer mistakes and are not forced to be handled.
Error is different — like OutOfMemoryError or StackOverflowError. These are JVM-level problems and should
never be caught in normal code."

Snippet 1 — Exception Hierarchy


// Complete hierarchy

/*
Throwable
├── Error (DON'T catch — JVM problems)
│ ├── OutOfMemoryError
│ └── StackOverflowError
└── Exception
├── RuntimeException (UNCHECKED — don't have to catch)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ ├── ClassCastException
│ ├── IllegalArgumentException
│ └── NumberFormatException
└── (Checked) (MUST handle or declare)
├── IOException
├── SQLException
├── FileNotFoundException
└── ClassNotFoundException
*/

Snippet 2 — try / catch / finally / multi-catch


// Complete exception handling syntax

public static int divide(int a, int b) {


try {
int result = a / b; // throws ArithmeticException if b=0
[Link]("Result: " + result);
return result;
} catch (ArithmeticException e) {
[Link]("Cannot divide by zero: " + [Link]());
return -1;
} catch (NullPointerException | IllegalArgumentException e) {
// Multi-catch (Java 7+) — handle multiple exceptions same way
[Link]("Input error: " + [Link]());
return -1;
} finally {
// ALWAYS runs — even if exception OR return statement!
// Use for cleanup: close streams, release locks
[Link]("Finally block always runs.");
}
}
divide(10, 2); // Result: 5 → Finally block always runs.
divide(10, 0); // Cannot divide by zero → Finally block always runs.

Snippet 3 — try-with-resources (Java 7+)


// Automatic resource closing — preferred way

// AutoCloseable resources are closed automatically — no need for finally!


try (FileReader fr = new FileReader("[Link]");
BufferedReader br = new BufferedReader(fr)) {

String line;
while ((line = [Link]()) != null) {
[Link](line);
}

} catch (IOException e) {
[Link]("Error reading file: " + [Link]());
}
// br and fr are automatically closed after try block
// even if an exception is thrown — no memory/file descriptor leak!

6.2 Custom Exceptions & throws


INTERVIEW ANSWER — Speak Exactly Like This
"We create custom exceptions to make error handling more meaningful and domain-specific.
Extend RuntimeException for unchecked exceptions (no forced handling).
Extend Exception for checked exceptions (must be handled or declared with throws).
Always add a message and optionally a cause (the original exception) to preserve the stack trace."

Snippet 1 — Custom Exception


// Domain-specific exception class

// Custom unchecked exception


public class InsufficientFundsException extends RuntimeException {
private final double required;
private final double available;

public InsufficientFundsException(double required, double available) {


super([Link]("Need %.2f but only %.2f available", required,
available));
[Link] = required;
[Link] = available;
}

// Preserve original cause (exception chaining)


public InsufficientFundsException(String msg, Throwable cause) {
super(msg, cause);
[Link] = [Link] = 0;
}

public double getRequired() { return required; }


public double getAvailable() { return available; }
}

// Usage
void withdraw(double amount) {
if (amount > balance) {
throw new InsufficientFundsException(amount, balance);
}
balance -= amount;
}
07. Generics
7.1 Why Generics?
INTERVIEW ANSWER — Speak Exactly Like This
"Generics were introduced in Java 5 to provide compile-time type safety without casting.
Before generics, collections held Object references, so you could accidentally mix types and get
ClassCastException at runtime.
With generics, the type is checked at compile time. The angle bracket syntax like List<String> means this list
can ONLY hold String objects.
At runtime, generics are erased (called type erasure) — the bytecode just uses Object. This is for backward
compatibility."

Snippet 1 — Generic Class


// Type-safe container

public class Pair<A, B> {


private final A first;
private final B second;

public Pair(A first, B second) {


[Link] = first;
[Link] = second;
}

public A getFirst() { return first; }


public B getSecond() { return second; }

@Override
public String toString() { return "(" + first + ", " + second + ")"; }
}

Pair<String, Integer> student = new Pair<>("Alice", 95);


Pair<Double, Boolean> result = new Pair<>(3.14, true);

String name = [Link](); // no cast needed!


int score = [Link](); // compiler knows it's Integer
[Link](student); // (Alice, 95)

Snippet 2 — Generic Method & Bounded Types


// Restricting type parameters with extends

// Bounded type: T must be Comparable


public static <T extends Comparable<T>> T findMax(List<T> list) {
if ([Link]()) throw new IllegalArgumentException("Empty list");
T max = [Link](0);
for (T item : list) {
if ([Link](max) > 0) max = item;
}
return max;
}

[Link](findMax([Link](3,1,4,1,5,9,2))); // 9
[Link](findMax([Link]("mango","apple","kiwi"))); // mango

// Multiple bounds
public <T extends Comparable<T> & Cloneable> void process(T item) { }
Snippet 3 — Wildcards (PECS Rule)
// Upper and lower bounded wildcards

// ? extends T — PRODUCER (read-only): you can READ T from it


double sumList(List<? extends Number> list) {
double sum = 0;
for (Number n : list) sum += [Link]();
return sum;
}
sumList([Link](1, 2, 3)); // accepts List<Integer>
sumList([Link](1.1, 2.2, 3.3)); // accepts List<Double>

// ? super T — CONSUMER (write-only): you can WRITE T into it


void addNumbers(List<? super Integer> list) {
[Link](1); [Link](2); [Link](3);
}
addNumbers(new ArrayList<Number>()); // accepts List<Number>
addNumbers(new ArrayList<Object>()); // accepts List<Object>

// PECS = Producer Extends, Consumer Super


// If you read from collection → ? extends T
// If you write to collection → ? super T
08. Functional Programming — Lambdas, Streams &
Optional
8.1 Lambda Expressions
INTERVIEW ANSWER — Speak Exactly Like This
"Lambda expressions were introduced in Java 8. They allow you to write anonymous functions — functions
without a name.
A lambda can be used wherever a functional interface is expected — that is, an interface with exactly one
abstract method.
The syntax is: (parameters) arrow expression.
Lambdas make the code shorter and more readable, especially when working with the Streams API and event
handling."

Snippet 1 — Lambda Syntax Variations


// All lambda syntax forms

// No parameters
Runnable r = () -> [Link]("Running!");
[Link]();

// One parameter (parens optional)


Consumer<String> print = s -> [Link](s);
Consumer<String> print2 = [Link]::println; // method reference

// Multiple parameters
Comparator<String> byLength = (a, b) -> [Link]() - [Link]();

// Block body (multiple statements)


Function<Integer, String> grade = score -> {
if (score >= 90) return "A";
if (score >= 80) return "B";
return "C";
};

[Link]([Link](85)); // B

Snippet 2 — Built-in Functional Interfaces


// The 4 core functional interfaces in [Link]

import [Link].*;

// Function<T, R> — takes T, returns R


Function<String, Integer> length = String::length;
[Link]("Hello"); // 5

// Predicate<T> — takes T, returns boolean


Predicate<String> isLong = s -> [Link]() > 5;
[Link]("Hi"); // false
[Link]("Hello World"); // true

// Consumer<T> — takes T, returns nothing (side effect)


Consumer<String> logger = s -> [Link]("LOG: " + s);
[Link]("test"); // LOG: test

// Supplier<T> — takes nothing, returns T (factory)


Supplier<List<String>> listFactory = ArrayList::new;
List<String> list = [Link]();

// BiFunction<T, U, R>, BiPredicate, BiConsumer


BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
[Link](3, 4); // 7

8.2 Stream API


INTERVIEW ANSWER — Speak Exactly Like This
"The Stream API allows you to process collections in a functional, pipeline style.
A stream has three parts: a source (collection, array, or generator),
intermediate operations (filter, map, sorted — these are lazy, they do not execute until terminal),
and terminal operations (collect, forEach, reduce, count — these trigger the pipeline).
Streams are single-use — once consumed, they cannot be reused.
They can be parallelised easily with parallelStream()."

Snippet 1 — Stream Pipeline


// filter → map → collect pattern

import [Link].*;

List<String> names = [Link]("Alice","Bob","Charlie","David","Eve");

// Pipeline: filter → map → sorted → collect


List<String> result = [Link]()
.filter(n -> [Link]() > 3) // keep: Alice, Charlie, David
.map(String::toUpperCase) // ALICE, CHARLIE, DAVID
.sorted() // ALICE, CHARLIE, DAVID (already
sorted)
.collect([Link]());

[Link](result); // [ALICE, CHARLIE, DAVID]

// Other terminals
long count = [Link]().filter(n -> [Link]("A")).count(); // 1
Optional<String> first = [Link]().filter(n -> [Link]() >
4).findFirst();
boolean anyMatch = [Link]().anyMatch(n -> [Link]("li")); // true
boolean allMatch = [Link]().allMatch(n -> [Link]() > 2); // true

Snippet 2 — reduce, groupingBy, joining


// Powerful terminal and collector operations

List<Integer> nums = [Link](1,2,3,4,5,6,7,8,9,10);

// reduce — fold all elements into one value


int sum = [Link]().reduce(0, Integer::sum); // 55
Optional<Integer> product = [Link]().reduce((a,b) -> a*b); // 3628800

// [Link]
List<String> words = [Link]("java","is","fun","and","powerful");
Map<Integer, List<String>> byLength = [Link]()
.collect([Link](String::length));
// {4=[java, powerful is wrong... let me fix] — groups by word length}

// [Link]
String joined = [Link]()
.collect([Link](", ", "[", "]"));
// [java, is, fun, and, powerful]

// Statistics
IntSummaryStatistics stats = [Link]()
.mapToInt(Integer::intValue).summaryStatistics();
[Link]([Link]()); // 10
[Link]([Link]()); // 5.5
[Link]([Link]()); // 55

Snippet 3 — Optional — Null Safety


// Avoid NullPointerException with Optional

// Optional wraps a value that may or may not be present


Optional<String> opt = [Link]("Hello");
Optional<String> empty = [Link]();
Optional<String> nullable = [Link](null); // safe

// Checking & getting


[Link](); // true
[Link](); // false (Java 11+)
[Link](); // "Hello" (throws if empty!)
[Link]("default"); // "Hello"
[Link]("default"); // "default"
[Link](() -> compute()); // lazy — compute only if empty
[Link](RuntimeException::new); // throw if empty

// Transforming
[Link](String::toUpperCase); // Optional<"HELLO">
[Link](s -> [Link]() > 3); // Optional<"Hello">
[Link](s -> findUser(s)); // avoids Optional<Optional<>>

// Side effects
[Link]([Link]::println); // prints Hello
[Link]( // Java 9+
[Link]::println,
() -> [Link]("Empty!")
);
09. Multithreading & Concurrency
9.1 Creating Threads
INTERVIEW ANSWER — Speak Exactly Like This
"In Java, a thread is a lightweight unit of execution. There are three ways to create one:
1. Extend Thread class and override the run() method.
2. Implement Runnable interface — preferred because Java does not support multiple inheritance, so extending
Thread wastes your one inheritance slot.
3. Use Callable with ExecutorService when you need a return value or want to handle checked exceptions.
Always prefer ExecutorService over raw threads in production code — it manages thread pool lifecycle
properly."

Snippet 1 — Three Ways to Create Threads


// Thread, Runnable, and Callable

// WAY 1: Extend Thread (limited — wastes inheritance)


class MyThread extends Thread {
@Override public void run() {
[Link]("Thread: " + [Link]().getName());
}
}
new MyThread().start();

// WAY 2: Implement Runnable (preferred for simple tasks)


Runnable task = () -> [Link]("Runnable running!");
new Thread(task, "worker-1").start();

// WAY 3: Callable + Future (returns result)


import [Link].*;
ExecutorService exec = [Link](4);

Callable<Integer> callable = () -> {


[Link](1000);
return 42;
};

Future<Integer> future = [Link](callable);


[Link]("Doing other work...");
[Link]("Result: " + [Link]()); // blocks until done
[Link]();

Snippet 2 — synchronized & Race Conditions


// Thread safety problem and solution

// PROBLEM: Race condition — without sync


class UnsafeCounter {
int count = 0;
void increment() { count++; } // NOT atomic! read-modify-write
}

// SOLUTION 1: synchronized method


class SafeCounter {
private int count = 0;
public synchronized void increment() { count++; }
public synchronized int getCount() { return count; }
}
// SOLUTION 2: AtomicInteger (lock-free, faster)
import [Link].*;
AtomicInteger ai = new AtomicInteger(0);
[Link](); // atomic read + increment
[Link](1, 2); // CAS: if value==1, set to 2
[Link](5); // add 5, return new value

Snippet 3 — CompletableFuture (Java 8+)


// Async programming made readable

import [Link].*;

// Chain async operations


CompletableFuture<String> cf = CompletableFuture
.supplyAsync(() -> fetchUserFromDB(userId)) // async task
.thenApply(user -> [Link]()) // transform result
.thenApply(name -> "Hello, " + name + "!") // another transform
.exceptionally(ex -> "Error: " + [Link]()); // handle exception

// Get result (blocks)


String greeting = [Link]();

// Combine two futures


CompletableFuture<String> f1 = [Link](() -> "Hello");
CompletableFuture<String> f2 = [Link](() -> "
World");
CompletableFuture<String> combined = [Link](f2, (a, b) -> a + b);
[Link]([Link]()); // Hello World

// Run all, wait for all


[Link](f1, f2).join();
10. Design Patterns
10.1 Singleton Pattern
INTERVIEW ANSWER — Speak Exactly Like This
"Singleton ensures that a class has only ONE instance throughout the application lifecycle.
It is used for shared resources like database connections, configuration managers, or logging.
The thread-safe way to implement it is using double-checked locking with volatile,
or even simpler — using an enum, which Java guarantees to be a singleton."

Snippet 1 — Thread-safe Singleton


// Double-checked locking with volatile

public class DatabaseConnection {


// volatile ensures changes visible across threads
private static volatile DatabaseConnection instance;
private Connection connection;

private DatabaseConnection() {
// expensive setup
connection = [Link](DB_URL);
}

public static DatabaseConnection getInstance() {


if (instance == null) { // first check (no lock)
synchronized ([Link]) {
if (instance == null) { // second check (with
lock)
instance = new DatabaseConnection();
}
}
}
return instance;
}

public Connection getConnection() { return connection; }


}

Snippet 2 — Enum Singleton (Best Practice)


// Simplest and safest singleton

// Enum Singleton — thread-safe, serialization-safe, reflection-proof


public enum AppConfig {
INSTANCE;

private final String dbUrl;


private final int maxConnections;

AppConfig() {
dbUrl = [Link]("DB_URL");
maxConnections = 10;
}

public String getDbUrl() { return dbUrl; }


public int getMaxConnections() { return maxConnections; }
}

// Usage
String url = [Link]();

10.2 Builder Pattern


INTERVIEW ANSWER — Speak Exactly Like This
"Builder pattern is used when an object has many optional parameters.
Instead of a constructor with 10 parameters (which is confusing and error-prone),
you use a Builder inner class that lets you set only the fields you need, in any order.
This is widely used in Java — StringBuilder, [Link], [Link] (Java 11) all use this pattern."

Snippet 1 — Builder Pattern


// Fluent API for object construction

public class HttpRequest {


private final String url;
private final String method;
private final Map<String,String> headers;
private final String body;
private final int timeoutMs;

private HttpRequest(Builder b) {
url = [Link];
method = [Link];
headers = [Link]([Link]);
body = [Link];
timeoutMs = [Link];
}

public static class Builder {


private final String url; // required
private String method = "GET"; // optional
defaults
private Map<String,String> headers = new HashMap<>();
private String body = "";
private int timeoutMs = 5000;

public Builder(String url) { [Link] = url; }


public Builder method(String m) { method=m; return this; }
public Builder header(String k,String v){ [Link](k,v); return
this; }
public Builder body(String b) { body=b; return this; }
public Builder timeout(int ms) { timeoutMs=ms; return this; }
public HttpRequest build() { return new
HttpRequest(this); }
}
}

HttpRequest req = new [Link]("[Link]


.method("POST")
.header("Content-Type", "application/json")
.body("{\"name\":\"Alice\"}")
.timeout(3000)
.build();

10.3 Strategy Pattern


Snippet 1 — Strategy with Lambdas
// Swappable algorithms at runtime
@FunctionalInterface
interface PaymentStrategy {
boolean pay(double amount);
}

class ShoppingCart {
private double total = 150.0;
private PaymentStrategy strategy;

void setPaymentStrategy(PaymentStrategy s) { strategy = s; }

void checkout() {
if ([Link](total)) {
[Link]("Payment successful: $" + total);
}
}
}

// Inject different strategies at runtime


ShoppingCart cart = new ShoppingCart();

[Link](amount -> {
[Link]("Paying $" + amount + " with Credit Card");
return true;
});
[Link]();

[Link](amount -> {
[Link]("Paying $" + amount + " with PayPal");
return amount <= 1000;
});
[Link]();
11. JVM Memory Model & Garbage Collection
11.1 Memory Areas
INTERVIEW ANSWER — Speak Exactly Like This
"The JVM divides memory into several areas:
Heap — where all objects live. This is what the Garbage Collector manages.
Stack — per-thread. Holds local variables, method call frames, and primitive values.
Metaspace (Java 8+, replaced PermGen) — stores class metadata and static variables.
Code Cache — stores JIT-compiled native code.
When a variable is a primitive, its value lives directly on the stack. When it is an object, the reference (address)
lives on the stack, and the actual object is on the heap."

Snippet 1 — Stack vs Heap Illustrated


// Where values actually live

public void example() {


int x = 10; // x (value 10) lives on STACK
String s = "Hello"; // s (reference) on STACK, "Hello" on HEAP
Person p = new Person(); // p (reference) on STACK, Person object on
HEAP
}
// When example() returns, stack frame is popped
// x and s references are gone
// Person object on heap becomes eligible for GC (no more references)

// Stack overflow — too many method calls (infinite recursion)


void infinite() { infinite(); } // StackOverflowError!

// OutOfMemoryError — heap is full


List<byte[]> leak = new ArrayList<>();
while(true) [Link](new byte[1024*1024]); // OutOfMemoryError

Snippet 2 — Memory Leaks in Java


// Common causes of memory leaks

// LEAK 1: Static reference holds object alive


public class Cache {
private static final Map<String, Object> store = new HashMap<>();
// If you keep adding without removing, objects never get GC'd
// FIX: Use WeakHashMap or limit cache size
private static final Map<String, Object> safeStore = new
WeakHashMap<>();
}

// LEAK 2: Listener never removed


[Link](e -> { /* heavy work */ });
// FIX: remove listener when done
[Link](listener);

// LEAK 3: Not closing streams


// BAD:
FileInputStream fis = new FileInputStream("[Link]"); // never closed = FD
leak
// GOOD — try-with-resources:
try (FileInputStream fis2 = new FileInputStream("[Link]")) { }

11.2 Garbage Collection


INTERVIEW ANSWER — Speak Exactly Like This
"Garbage Collection automatically reclaims memory from objects that are no longer reachable.
An object is eligible for GC when no active thread holds a reference to it.
Java has several GC algorithms: Serial, Parallel, G1 (default since Java 9), and ZGC (ultra-low latency).
You cannot force GC — [Link]() is just a hint. The JVM decides when to actually run it.
GC pauses are called Stop-The-World events — the whole application pauses while GC runs."

Snippet 1 — Object Lifecycle


// When objects get garbage collected

// Objects become eligible for GC when:

// 1. Reference set to null


Person p = new Person("Alice");
p = null; // Alice object now eligible for GC

// 2. Reference goes out of scope


{
Person temp = new Person("Bob");
} // temp out of scope — Bob eligible for GC

// 3. Re-assigned reference
Person ref = new Person("Charlie");
ref = new Person("Dave"); // Charlie now eligible for GC

// 4. Island of isolation (circular references — GC handles this!)


class Node { Node next; }
Node a = new Node(); Node b = new Node();
[Link] = b; [Link] = a;
a = null; b = null; // Both eligible despite circular reference
// GC uses reachability from GC roots, NOT reference counting
12. Modern Java — Java 8 to Java 21+
12.1 Java 8 — The Revolution
Java 8 (2014) was the biggest update since Java 5. It introduced Lambdas, Streams, Optional, and the new
Date/Time API.

Snippet 1 — New Date & Time API ([Link])


// Replacing [Link] and Calendar

import [Link].*;
import [Link].*;

// Immutable, thread-safe date/time classes


LocalDate date = [Link](); // 2025-04-27
LocalTime time = [Link](); // 14:30:15.123
LocalDateTime dt = [Link](); // 2025-04-27T14:30:15
ZonedDateTime zdt = [Link]([Link]("Asia/Kolkata"));

// Creating specific dates


LocalDate birthday = [Link](1995, [Link], 23);
LocalDate next = [Link](1).withMonth(6);

// Comparing
[Link]([Link]()); // true
Period age = [Link](birthday, [Link]());
[Link]([Link]() + " years old");

// Formatting
DateTimeFormatter fmt = [Link]("dd/MM/yyyy");
[Link]([Link](fmt)); // 23/05/1995
LocalDate parsed = [Link]("27/04/2025", fmt);

12.2 Java 9-11 — Quality of Life


Snippet 1 — New String Methods (Java 11)
// String improvements in Java 11

// Java 11 additions
" hello ".strip(); // "hello" (better than trim — handles Unicode)
" hello ".stripLeading(); // "hello "
" ".isBlank(); // true (only whitespace)
"line1\nline2\nline3".lines().collect([Link]()); //
Stream<String>
"Java".repeat(3); // "JavaJavaJava"

// Java 9 — Collection factory methods


List<String> fixedList = [Link]("a", "b", "c");
Set<Integer> fixedSet = [Link](1, 2, 3);
Map<String,Integer> fixedMap = [Link]("one",1, "two",2);
// [Link]("d"); // UnsupportedOperationException!

// Java 10 — var
var map = new HashMap<String, List<Integer>>(); // type inferred

12.3 Java 14-17 — Records, Sealed, Pattern Matching


Snippet 1 — Records (Java 16+)
// Immutable data classes with zero boilerplate

// Before records — need constructor, getters, equals, hashCode, toString


// After records — one line!

public record Point(double x, double y) {


// Compact constructor — for validation
Point {
if ([Link](x) || [Link](y))
throw new IllegalArgumentException("NaN not allowed");
}

// Custom method
public double distanceFromOrigin() {
return [Link](x, y);
}
}

Point p = new Point(3.0, 4.0);


[Link](p.x()); // 3.0 (accessor method)
[Link]([Link]()); // 5.0
[Link](p); // Point[x=3.0, y=4.0]
[Link]([Link](new Point(3,4))); // true (auto-generated)

Snippet 2 — Sealed Classes + Pattern Matching Switch (Java 17/21)


// Exhaustive type hierarchies

// Sealed class — only listed subtypes allowed


public sealed interface Shape permits Circle, Rectangle, Triangle {}

record Circle (double radius) implements Shape {}


record Rectangle(double width, double height) implements Shape {}
record Triangle (double a, double b, double c) implements Shape {}

// Pattern matching switch — no default needed (exhaustive!)


double area(Shape s) {
return switch (s) {
case Circle c -> [Link] * [Link]() * [Link]();
case Rectangle r -> [Link]() * [Link]();
case Triangle t -> {
double sp = (t.a() + t.b() + t.c()) / 2;
yield [Link](sp * (sp-t.a()) * (sp-t.b()) * (sp-t.c()));
}
};
}

[Link](area(new Circle(5))); // 78.54...


[Link](area(new Rectangle(4, 6))); // 24.0

12.4 Java 21 — Virtual Threads


INTERVIEW ANSWER — Speak Exactly Like This
"Virtual Threads were introduced in Java 21 as part of Project Loom.
Traditional platform threads are expensive — each maps to an OS thread, and you can only have thousands.
Virtual threads are lightweight — they are managed by the JVM, not the OS, so you can have MILLIONS of
them.
They are perfect for I/O-bound tasks like HTTP requests or database calls.
The best part: your existing code using Thread or ExecutorService works with virtual threads with minimal
changes."
Snippet 1 — Virtual Threads
// Project Loom — Java 21

// Create a single virtual thread


Thread vt = [Link]().start(() -> {
[Link]("Virtual: " + [Link]().isVirtual()); //
true
});
[Link]();

// ExecutorService with virtual threads — preferred for servers


try (ExecutorService exec = [Link]()) {
for (int i = 0; i < 100_000; i++) {
int task = i;
[Link](() -> handleRequest(task)); // 100,000 virtual threads!
}
} // auto-shuts down

// With platform threads, 100,000 threads would crash the JVM


// Virtual threads handle this effortlessly
13. Top Interview Questions & Model Answers
INTERVIEWER ASKS Q1: What is the difference between == and equals() in Java?

INTERVIEW ANSWER — Speak Exactly Like This


"== compares references — it checks if both variables point to the SAME memory location.
equals() compares content — it checks if the values are logically equal.
For primitives, == compares values directly.
For Strings and all objects, always use equals() to compare content.
If you override equals(), you MUST also override hashCode() — this is the contract."

INTERVIEWER ASKS Q2: What is the difference between abstract class and interface?

INTERVIEW ANSWER — Speak Exactly Like This


"Abstract class can have constructors, instance variables, concrete methods, and abstract methods.
Interface can only have abstract methods, constants, default methods (Java 8+), and static methods.
A class can extend only ONE abstract class, but can implement MULTIPLE interfaces.
Use abstract class when subclasses share common state or code.
Use interface when defining a capability or contract across unrelated classes."

INTERVIEWER ASKS Q3: Explain HashMap internal working.

INTERVIEW ANSWER — Speak Exactly Like This


"HashMap uses an array of buckets internally. When you call put(key, value):
1. It calls hashCode() on the key to get a hash.
2. It maps the hash to a bucket index using hash & (capacity-1).
3. If the bucket is empty, it places the entry there.
4. If there is a collision (two keys map to same bucket), it adds to a linked list (or TreeNode after 8 entries).
get() follows the same hash to find the bucket, then uses equals() to find the exact entry.
Default capacity is 16, load factor 0.75 — it rehashes when 75% full."

INTERVIEWER ASKS Q4: What is the difference between checked and unchecked exceptions?

INTERVIEW ANSWER — Speak Exactly Like This


"Checked exceptions extend Exception (not RuntimeException). The compiler FORCES you to handle them
with try-catch or declare them with throws. Examples: IOException, SQLException.
Unchecked exceptions extend RuntimeException. The compiler does NOT force you to handle them. They
represent programmer errors. Examples: NullPointerException, ArrayIndexOutOfBoundsException.
As a general rule: use checked exceptions for recoverable conditions, unchecked for programming bugs."

INTERVIEWER ASKS Q5: What is method overriding vs overloading?

INTERVIEW ANSWER — Speak Exactly Like This


"Overloading is compile-time polymorphism — same method name, different parameter types or count, in the
same class.
Overriding is runtime polymorphism — same method name and signature in a subclass that replaces the parent
behaviour.
Overloading is resolved at compile time by the compiler. Overriding is resolved at runtime by the JVM using
dynamic dispatch.
For overriding: access modifier cannot be more restrictive, return type must be same or covariant, and static
methods CANNOT be overridden (they are hidden)."

INTERVIEWER ASKS Q6: What is the Java Memory Model? Explain heap and stack.

INTERVIEW ANSWER — Speak Exactly Like This


"Stack is per-thread memory that stores local variables, method call frames, and primitive values.
When a method is called, a new frame is pushed. When it returns, the frame is popped.
Heap is shared memory where all objects live. It is managed by the Garbage Collector.
When I write int x = 5, the value 5 lives on the stack.
When I write Person p = new Person(), the reference p lives on the stack, but the Person object itself lives on
the heap."

INTERVIEWER ASKS Q7: What is String immutability and why is it important?

INTERVIEW ANSWER — Speak Exactly Like This


"Once a String is created, its content cannot be changed. Any operation that appears to modify it actually
creates a new String object.
This matters for 3 reasons:
1. Thread safety — immutable objects can be safely shared across threads without synchronisation.
2. Security — method parameters like passwords or file paths cannot be altered by other code.
3. HashMap keys — since the hash code cannot change, Strings make reliable HashMap keys.
For mutable string operations in loops, use StringBuilder which is NOT immutable."

INTERVIEWER ASKS Q8: What is the difference between ArrayList and LinkedList?

INTERVIEW ANSWER — Speak Exactly Like This


"ArrayList is backed by a dynamic array.
Random access (get by index) is O(1) — direct array lookup.
Insert/delete in the middle is O(n) — must shift elements.
LinkedList is a doubly-linked list.
Insert/delete at head or tail is O(1).
Random access is O(n) — must traverse nodes.
In practice, ArrayList is faster for most use cases due to CPU cache locality.
Use LinkedList as a Deque (double-ended queue) for stack/queue operations."

You might also like