0% found this document useful (0 votes)
0 views32 pages

Java Complete Interview Notes

This document serves as a comprehensive revision guide for Java interview preparation, covering key topics such as JVM internals, memory architecture, garbage collection, and object-oriented programming principles. It outlines the roles of JDK, JRE, and JVM, details the JVM architecture and execution flow, and explains memory management concepts including stack and heap. Additionally, it discusses OOP concepts, method overloading vs overriding, and provides best practices and common pitfalls to avoid in Java programming.

Uploaded by

HEAD SET
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)
0 views32 pages

Java Complete Interview Notes

This document serves as a comprehensive revision guide for Java interview preparation, covering key topics such as JVM internals, memory architecture, garbage collection, and object-oriented programming principles. It outlines the roles of JDK, JRE, and JVM, details the JVM architecture and execution flow, and explains memory management concepts including stack and heap. Additionally, it discusses OOP concepts, method overloading vs overriding, and provides best practices and common pitfalls to avoid in Java programming.

Uploaded by

HEAD SET
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 — Complete Interview Revision

Notes
JVM Internals · OOP · Collections · Memory · Concurrency · Tricky Cases
Intermediate to Advanced — Weekly Revision Document

Legend: ⚠️= common trap/pitfall ✅ = best practice 📝 = must remember

1. JDK, JRE & JVM — Internals In Depth

1.1 The Three Layers


Most people say 'JDK contains JRE which contains JVM' and stop there. That is necessary but not
sufficient for interviews.

JDK — Java Development Kit


• Everything a developer needs: compiler (javac), debugger (jdb), jar tool, javadoc, JRE.
• javac compiles .java source → .class bytecode (platform-independent).
• The .class file contains JVM instructions, not native machine code.

JRE — Java Runtime Environment


• What an end-user needs to RUN a compiled Java program.
• Contains: JVM + core class libraries ([Link], [Link], [Link], etc.) + supporting files.
• Does NOT contain javac. You cannot compile with only JRE.

JVM — Java Virtual Machine


• The runtime engine that actually executes bytecode.
• Platform-specific (Windows JVM vs Linux JVM) but executes the same bytecode — Write Once
Run Anywhere.
• Handles: class loading, bytecode verification, JIT compilation, memory management, GC, thread
management.

1.2 JVM Architecture — Full Internal Flow


When you run 'java MyClass', this is the exact sequence:

Step 1 — Class Loader Subsystem


• Bootstrap ClassLoader: loads core Java classes ([Link].*, [Link]). Written in native C/C++. Parent
of all loaders.
• Extension ClassLoader: loads classes from JRE/lib/ext directory.
• Application ClassLoader: loads your application classes from classpath.
• ClassLoaders follow Delegation Model: always ask parent first; load only if parent fails.

Page | 1
// ClassLoader delegation:
// AppClassLoader → ExtClassLoader → BootstrapClassLoader → (not found) →
back down
⚠️ If you create two ClassLoaders and load the same class in both, they produce DIFFERENT
Class objects. instanceof will fail across loaders.

Step 2 — Bytecode Verifier


• Verifies .class file structure, checks for illegal code that could breach security.
• Ensures: no stack overflows, no illegal type conversions, correct method signatures.

Step 3 — JVM Runtime Data Areas (Memory Regions)


• Method Area (Metaspace in Java 8+): class metadata, static variables, constant pool, method
bytecode. Shared across threads.
• Heap: all object instances and arrays. Shared across threads. GC operates here.
• Stack: each thread has its own JVM stack. Each method call creates a stack frame containing local
variables, operand stack, frame data.
• PC Register: each thread has its own Program Counter — holds address of current JVM instruction.
• Native Method Stack: supports native (C/C++) method calls.

Step 4 — Execution Engine


• Interpreter: reads and executes bytecode instruction by instruction. Fast startup, slow execution.
• JIT Compiler (Just-In-Time): identifies 'hot' code (frequently executed loops/methods), compiles to
native machine code at runtime. Dramatically faster execution.
• HotSpot JVM uses tiered compilation: C1 (fast, less optimised) then C2 (slower compile, highly
optimised) for very hot code.
• Garbage Collector: runs as part of execution engine to reclaim memory.
📝 JIT compilation is why Java is often comparable in speed to C++ for long-running server
applications.

1.3 What Happens When You Write 'public static void main(String[] args)'

The Anatomy of main()


public class Main {
public static void main(String[] args) {
// your code
}
}

• public: JVM must be able to call this from outside the class. Must be public.
• static: JVM calls main() without creating an object of the class. Must be static.
• void: JVM does not use a return value from main(). Must be void.
• String[] args: command-line arguments passed as an array of Strings. Can also write String... args
(varargs).

⚠️ If main() is missing, misspelled, or has wrong signature — runtime error: 'Main method not
found in class'

Page | 2
⚠️ String args[] and String[] args are IDENTICAL — both compile and run correctly.
✅ [Link] == 0 if no command-line arguments are passed. args[0] is the first argument, NOT the
program name (unlike C).

1.4 [Link] — Internals

This one line involves several layers. Understanding it shows deep Java knowledge.

What is System?
• [Link] is a final class — cannot be subclassed.
• All fields and methods are static — you never create an instance. That's why no 'new System()'.
• Has three pre-connected streams: [Link] (InputStream), [Link] (PrintStream), [Link]
(PrintStream).
• [Link] is declared as: public static final PrintStream out — initialized by the JVM on startup,
connected to the OS standard output (console/terminal).

What is PrintStream?
• [Link] extends FilterOutputStream which extends OutputStream.
• Provides println(), print(), printf() methods.
• println(x) calls print(x) then println() — print() converts argument to String via [Link](),
then writes bytes to underlying OutputStream.
• The OutputStream ultimately makes a native system call to write to the OS file descriptor 1
(stdout).

Why no 'new System()' but 'new Scanner()'?


This is a very common interview question. The answer:
• [Link] is already a static, pre-created PrintStream object. It is initialised by JVM before main()
runs. You just reference the existing object.
• Scanner wraps an input source that YOU choose ([Link], a File, a String). It needs to know
WHAT to read from — hence you pass that source as a constructor argument.
• Scanner has state (position, buffered data, delimiter pattern) that is per-instance. [Link] is a
single shared stream.

1.5 Scanner Class — Internals

Class Hierarchy
[Link]
implements Iterator<String>
implements Closeable
implements AutoCloseable

How Scanner works internally


• Scanner wraps a Readable source (InputStream, File, String, Channel).

Page | 3
• For [Link], it wraps an InputStreamReader which reads bytes and decodes them to characters
using platform default charset.
• Internally maintains a CharBuffer that buffers input read from the Readable source.
• Uses a regex pattern as delimiter (default: whitespace pattern '\p{javaWhitespace}+').
• next() and nextLine() use this internal buffer and delimiter to return tokens.

Newline trap — the most common Scanner bug


Scanner sc = new Scanner([Link]);
int n = [Link](); // reads '5' but leaves '\n' in buffer
String s = [Link](); // reads the leftover '\n' — returns empty
string!
// Fix: add [Link]() after nextInt() to consume the \n
⚠️ nextInt(), nextDouble(), next() do NOT consume the newline. nextLine() reads until and
INCLUDING \n. This mismatch causes the classic empty-string bug.

Why you should close Scanner


• [Link]() closes the underlying Readable source.
• If source is [Link], closing Scanner closes [Link] — you cannot read from [Link] again
in that JVM session.
✅ For competitive programming, closing Scanner is optional. For production code, use try-with-
resources: try(Scanner sc = new Scanner([Link])) { ... }

Page | 4
2. Memory Architecture — Heap, Stack, Metaspace

2.1 The Stack

Structure
• Each thread has its own private stack. No two threads share a stack.
• Stack is divided into frames. One frame per method call.
• Frame contains: local variable array, operand stack (for intermediate calculations), reference to
constant pool.

What goes on the stack


• Primitive local variables: int, double, boolean, char, long, float, byte, short.
• Reference variables (NOT the object itself — just the 4-byte or 8-byte reference/pointer).
• Method call information (return address, parameters).

Stack behaviour
void methodA() {
int x = 10; // x is on stack
String s = "hello"; // s (reference) on stack, object on heap
methodB(); // new frame pushed
} // frame popped, x and s reference destroyed

⚠️ Stack is LIFO. When a method returns, its frame is popped. Local variables are gone.
⚠️ StackOverflowError occurs when too many frames accumulate (infinite recursion).
✅ Stack is thread-safe by design — no thread can access another thread's stack.

2.2 The Heap

Structure — Generational Heap


• Young Generation: where new objects are born.
• → Eden Space: all new objects created here via 'new'.
• → Survivor Space S0 and S1 (From and To): objects that survive Minor GC move here.
• Old Generation (Tenured): long-lived objects promoted from Young Generation.
• Metaspace (Java 8+): class metadata, static variables, interned Strings. Not on heap. Uses native
memory.

What goes on the heap


• All object instances created with 'new'.
• All arrays (even arrays of primitives — the array object is on heap, elements inside it).
• Static variables (stored in Metaspace / special heap area since Java 8).
• String pool (interned strings) — part of heap since Java 7.

Page | 5
Heap vs Stack — Quick Comparison
Feature Stack Heap
Scope Method-local JVM-wide (shared)
Thread access Thread-private Shared across threads
Size Small (1-8 MB typical) Large (Xmx configurable)
Speed Very fast (LIFO pointer) Slower (GC, allocation)
Lifetime Until method returns Until GC collects
Error StackOverflowError OutOfMemoryError
Managed by JVM automatically Garbage Collector

2.3 Garbage Collection — Deep Dive

When does GC run


• JVM decides — you cannot force it. [Link]() is a HINT, not a command.
• GC runs when: Eden is full (Minor GC), Old Gen is full (Major GC), or JVM needs memory.

Minor GC (Young Generation)


• Eden is full → Minor GC runs.
• Live objects in Eden → moved to S0 (Survivor). Dead objects → swept.
• Next Minor GC: live Eden + live S0 objects → moved to S1. S0 cleared.
• Objects that survive enough GC cycles (default: 15) → promoted to Old Generation.
• Minor GC is fast — Stop-the-World pause is typically milliseconds.

Major GC (Old Generation)


• Triggered when Old Generation fills up.
• Much slower — all live objects in Old Gen must be scanned.
• Uses Mark-Sweep-Compact: Mark live objects → Sweep dead ones → Compact remaining to
remove fragmentation.

GC Algorithms in Java
Serial GC (-XX:+UseSerialGC)
Single-threaded GC. Good for small apps, single-CPU. Stop-the-World for
all phases.

Parallel GC (-XX:+UseParallelGC) — default before Java 9


Multi-threaded Minor GC. Still Stop-the-World. Good throughput.

G1 GC (-XX:+UseG1GC) — default since Java 9


Divides heap into equal-sized regions (~1-32MB each).
Collects regions with most garbage first (Garbage First).
Concurrent marking, predictable pause times. Best general-purpose GC.

ZGC (-XX:+UseZGC) — Java 15+ production-ready


Sub-millisecond pause times regardless of heap size.
Fully concurrent — does almost all work without stopping threads.

Shenandoah GC (-XX:+UseShenandoahGC)

Page | 6
Similar to ZGC — concurrent compaction. Low pause times.

Object Eligibility for GC


• Object is eligible for GC when no live reference points to it.
String s = new String("hello");
s = null; // original object now eligible for GC

• Types of references affecting GC:


• Strong reference (default): Object NOT collected while strong ref exists.
• Soft reference (SoftReference<T>): Collected when JVM needs memory. Good for caches.
• Weak reference (WeakReference<T>): Collected at next GC cycle. Good for WeakHashMap.
• Phantom reference (PhantomReference<T>): After finalization, before memory reclaim. Low-
level cleanup.

⚠️ finalize() method is deprecated in Java 9, removed in Java 18. Do NOT rely on it for
cleanup. Use try-with-resources or Cleaner instead.
⚠️ Memory leaks in Java happen when objects are unintentionally referenced (static
collections, listeners not removed, ThreadLocal not cleaned).

JVM Memory Flags


-Xms512m // initial heap size
-Xmx2g // max heap size
-Xss256k // stack size per thread
-XX:MetaspaceSize=128m // initial Metaspace
-XX:MaxMetaspaceSize=256m // max Metaspace
-XX:+HeapDumpOnOutOfMemoryError // dump heap on OOM

Page | 7
3. OOP — Concepts, Internals & Tricky Cases

3.1 The Four Pillars

Encapsulation
• Binding data (fields) and methods that operate on data into one unit (class).
• Access control via private/protected/public modifiers.
• Getters and setters control access — allows validation before setting.

Abstraction
• Hiding implementation details, exposing only essential interface.
• Achieved via abstract classes and interfaces.
• Abstract class: partial implementation. Interface: pure contract (before Java 8).

Inheritance
• A class (subclass) acquires properties and behaviours of another (superclass).
• Java supports SINGLE inheritance for classes — a class can extend only ONE class.
• Java supports MULTIPLE inheritance through interfaces.
class Animal { void eat() { } }
class Dog extends Animal { void bark() { } } // Dog IS-A Animal

⚠️ Diamond Problem: If Java allowed class multiple inheritance and both parents had the same
method, which to inherit? Java solves this by allowing only single class inheritance. With
interfaces, default method conflict resolved by overriding in implementing class.

Polymorphism
• One interface, many implementations.
• Compile-time (Static) polymorphism: Method Overloading — same name, different parameters.
• Runtime (Dynamic) polymorphism: Method Overriding — subclass provides its own
implementation.

3.2 Method Overloading vs Overriding — Deep Rules

Overloading Rules
• Same class, same method name, DIFFERENT parameter list (type, count, or order).
• Return type alone is NOT sufficient to overload — compile error.
• Resolved at COMPILE TIME (static dispatch).
int add(int a, int b) { }
double add(double a, double b) { } // OK — different param types
int add(int b, int a) { } // NOT overloading — same params

⚠️ Autoboxing and widening can cause ambiguous overloading:


void test(Integer i) { }

Page | 8
void test(long i) { }
test(5); // calls test(long) — widening preferred over boxing

Overriding Rules
• Subclass provides its own implementation of superclass method.
• Method signature MUST be identical (name + parameters).
• Return type must be same OR covariant (subtype of original return type).
• Access modifier cannot be MORE restrictive (can be same or less restrictive).
• Cannot override static methods — hiding occurs instead.
• Cannot override final methods.
• Cannot override private methods — they are not inherited.
• Resolved at RUNTIME via vtable lookup (dynamic dispatch).

⚠️ Static methods belong to the class, not instances. 'Overriding' a static method just hides it.
The call is resolved by reference type, not object type:
Parent p = new Child();
[Link](); // calls Parent's static method — NOT Child's
[Link](); // calls Child's override — runtime polymorphism

3.3 Abstract Class vs Interface

Feature Abstract Class Interface


Instantiate? No No
Constructor? Yes No
Fields Any (instance, static) public static final
only
Methods Any (abstract + concrete) abstract, default,
static
Extends/implements extends (single) implements (multiple)
Inheritance Single class Multiple interfaces
Use when IS-A with shared code CAN-DO contract

Java 8+ Interface features


• default methods: concrete methods in interface with 'default' keyword. Allows backward
compatibility.
• static methods: utility methods in interface.
• Functional interface: exactly one abstract method. Enables lambda expressions.

When to use which — the rule


• Use abstract class: when related classes share code AND have IS-A relationship.
• Use interface: when unrelated classes need to share a contract (Comparable, Serializable,
Runnable).
✅ From Java 8 onwards, the line has blurred — but abstract class can have state (instance fields)
while interface cannot.

3.4 The Object Class — Methods Every Java Object Has

Page | 9
equals() and hashCode() — The Contract
Every Java object inherits equals() and hashCode() from [Link]. The default behaviour uses
reference equality (memory address). You almost always need to override both.

The hashCode-equals Contract — MUST know this


• Rule 1: If [Link](b) is true → [Link]() MUST equal [Link]().
• Rule 2: If [Link]() == [Link]() → [Link](b) MAY be true or false (hash collision is
OK).
• Rule 3: If [Link](b) is false → hash codes MAY or may not be equal.

⚠️ If you override equals() but NOT hashCode(), objects that are logically equal will have
different hash codes. They will be stored in different buckets in HashMap/HashSet. You will
NEVER find them with get() even though equals() says they match.

The toString() method


• Default: ClassName@hexHashCode — e.g., 'Dog@1b6d3586'. Not useful.
• Always override in your classes for meaningful output.
✅ @Override annotation ensures compiler checks you're actually overriding and not accidentally
overloading.

clone() method
• Creates a copy of the object. Class must implement Cloneable marker interface.
• [Link]() is protected and does shallow copy.
• Deep copy requires overriding clone() and copying mutable fields manually.
⚠️ clone() is considered broken by many Java experts. Prefer copy constructors or factory
methods.

3.5 'this' and 'super' keywords

this
• Refers to current instance of the class.
• Use to distinguish instance variable from local variable: [Link] = name
• this() — calls another constructor of same class (constructor chaining). Must be first statement.
public Person(String name) { this(name, 0); } // delegates
public Person(String name, int age) { [Link]=name; [Link]=age; }

super
• Refers to immediate parent class.
• [Link]() — calls parent's overridden method.
• super() — calls parent's constructor. Must be first statement in child constructor.
⚠️ If you do not call super() explicitly, Java automatically inserts super() as first statement. If
parent has no no-arg constructor, compile error.

Page | 10
3.6 final keyword

• final variable: value cannot be reassigned after initialisation. For objects, the reference is final but
the object itself can be mutated.
final int x = 10; // x cannot be changed
final List<String> l = new ArrayList<>();
[Link]("hello"); // OK — l itself not changed, contents changed
l = new ArrayList<>(); // COMPILE ERROR — reassigning l

• final method: cannot be overridden in subclass.


• final class: cannot be subclassed. String, Integer, all wrappers are final.
⚠️ final does NOT mean constant for objects. It means the reference cannot point to a different
object.

3.7 static keyword — Detailed

• static variable: one copy shared across all instances. Belongs to class, not object. Stored in
Metaspace.
• static method: called on class, not instance. Cannot access non-static (instance) fields or methods.
Cannot use 'this'.
• static block: runs once when class is first loaded. Used for complex static initialisation.
• static nested class: does not have implicit reference to outer class instance. Can be instantiated
without outer class object.

class Counter {
static int count = 0; // shared across all
static { [Link]("Class loaded"); } // runs once
Counter() { count++; }
}

⚠️ Calling a static method via an object reference compiles and runs BUT is misleading.
Counter c = null; [Link]; — this works and returns the static value, doesn't throw NPE! The
reference is not used.

Page | 11
4. Strings — Immutability, Pool & Tricky Cases

4.1 String Immutability


String objects in Java are immutable. Once created, the character sequence cannot be changed. Every
'modification' creates a new String object.

• Why immutable? Security (network connections, passwords), thread-safety (safe to share without
synchronisation), String pool efficiency, hashCode caching (hashCode computed once and cached).
• String stores chars in a private final char[] (Java 8) or private final byte[] (Java 9+ with compact
strings).

4.2 String Pool (Intern Pool)

• String literals (in double quotes) are stored in a pool in the Heap (since Java 7; was PermGen
before).
• JVM checks pool first. If same string exists, returns existing reference. No new object.
• 'new String()' ALWAYS creates a new object on heap, bypassing the pool.

String s1 = "hello"; // pool


String s2 = "hello"; // same pool object as s1
String s3 = new String("hello"); // new heap object
String s4 = [Link](); // moves s3 to pool (returns pool
reference)

s1 == s2 // true — same pool reference


s1 == s3 // false — s3 is a separate heap object
s1 == s4 // true — intern() returned pool reference
[Link](s3) // true — content comparison

⚠️ NEVER use == to compare String content. Always use .equals() or .equalsIgnoreCase(). This
is the #1 String bug in Java.

4.3 String vs StringBuilder vs StringBuffer

Feature String StringBuilder StringBuffer


Mutable? No Yes Yes
Thread-safe? Yes (immutable) No Yes (synchronized)
Speed Slow (+) Fastest Slower (sync overhead)
Storage String pool/heap Heap Heap

• String concatenation with + in a loop creates many intermediate String objects. Use StringBuilder.
• Java 8+: compiler optimises simple concatenations to [Link]() calls. But in loops,
explicit StringBuilder is still necessary.

// Bad — creates N intermediate String objects in loop


String result = "";
for(int i=0;i<1000;i++) result += i;

Page | 12
// Good — one StringBuilder
StringBuilder sb = new StringBuilder();
for(int i=0;i<1000;i++) [Link](i);
String result = [Link]();

4.4 Tricky String Cases

// 1. String comparison pitfall


Integer a = 127; Integer b = 127;
a == b // true — cached in Integer cache (-128 to 127)
Integer c = 128; Integer d = 128;
c == d // false — different objects above cache range

// 2. String + int
[Link]("Value: " + 1 + 2); // "Value: 12" (left to right)
[Link]("Value: " + (1+2)); // "Value: 3"
[Link](1 + 2 + " Value"); // "3 Value"

⚠️ String concatenation is left-to-right. Once a String is encountered, everything after is also


stringified.

// 3. switch on String (Java 7+) uses equals() internally


// 4. substring() memory leak (Java 6) — shared char array
// Java 7+ fixed this — substring creates new array

Page | 13
5. Collections Framework — Internals & Comparisons

5.1 Collection Hierarchy

Iterable
└─ Collection
├─ List: ArrayList, LinkedList, Vector, Stack
├─ Set: HashSet, LinkedHashSet, TreeSet
└─ Queue: LinkedList, ArrayDeque, PriorityQueue
Map (NOT a Collection)
├─ HashMap, LinkedHashMap, TreeMap
└─ Hashtable (legacy, synchronized)

5.2 ArrayList — Internals

• Backed by a dynamic Object[] array. Default initial capacity = 10.


• When full, grows by 50%: newCapacity = oldCapacity + (oldCapacity >> 1).
• Random access O(1). Insert/delete in middle O(n) — elements shift.
• Add at end: O(1) amortised (occasional resize is O(n) but amortised across many adds).

ArrayList<Integer> list = new ArrayList<>(); // capacity 10


// After 10 elements: grows to 15, copies array
// After 15 elements: grows to 22, copies array

✅ If you know the size upfront, use new ArrayList<>(initialCapacity) to avoid resizing.
⚠️ ArrayList is NOT thread-safe. Multiple threads writing simultaneously can corrupt internal
array. Use [Link]() or CopyOnWriteArrayList.

5.3 LinkedList — Internals

• Doubly-linked list. Each node: data + prev pointer + next pointer.


• Implements both List and Deque — can be used as list, queue, or deque.
• Insert/delete at ends: O(1). Insert/delete in middle: O(n) to traverse + O(1) to insert.
• Random access: O(n) — no indexing. get(i) traverses from head or tail (whichever is closer).

Feature ArrayList LinkedList


get(i) O(1) O(n)
add to end O(1) amort O(1)
add at index O(n) O(n) to find + O(1) insert
Memory Less (array) More (prev+next pointers per node)

5.4 HashMap — Internals (Most Common Interview Topic)

Internal Structure

Page | 14
• Array of Node<K,V>[] called 'table'. Default capacity = 16.
• Each Node contains: int hash, K key, V value, Node<K,V> next.

How put(key, value) works


• Step 1: Compute hashCode() of key.
• Step 2: Apply bit-spreading: hash = h ^ (h >>> 16). Mixes high and low bits to reduce collisions.
• Step 3: Compute bucket index: index = (n - 1) & hash. Fast because n is always power of 2.
• Step 4: If bucket empty: insert new Node.
• Step 5: If bucket occupied (collision): traverse linked list using .equals(). If key found: update
value. If not found: add new Node to list.
• Step 6: If linked list length >= 8 AND table size >= 64: convert list to Red-Black Tree (O(log n) vs
O(n)).
• Step 7: If size > capacity * loadFactor (0.75): rehash — create new table of 2x size, redistribute all
entries.

How get(key) works


• Compute hash → find bucket index → traverse list/tree using equals() → return value or null.

Critical rules for using objects as HashMap keys


• Must override both equals() AND hashCode() correctly.
• Keys should be IMMUTABLE. If you mutate a key after putting it in the map, its hashCode
changes, the entry moves to a different bucket but isn't there. Entry becomes permanently lost.

⚠️ HashMap is NOT thread-safe. ConcurrentHashMap should be used in multithreaded


environments.
⚠️ HashMap allows one null key (stored at index 0) and multiple null values. TreeMap does
NOT allow null keys.

Load factor and capacity


Default capacity = 16, default load factor = 0.75
Resize threshold = 16 * 0.75 = 12 — rehash after 12 entries
new HashMap<>(expectedSize, 0.75f) // tune for performance

5.5 HashSet — Internals


• HashSet is internally a HashMap where values are all a dummy PRESENT object.
• add(e) calls [Link](e, PRESENT). contains(e) calls [Link](e).
• O(1) average for add/remove/contains. O(n) in worst case (all collisions).

5.6 TreeMap and TreeSet — Internals


• Backed by a Red-Black Tree (self-balancing BST).
• O(log n) for put, get, remove, containsKey.
• Keys are sorted in natural order or by provided Comparator.
• Does NOT allow null keys (NullPointerException). TreeSet same — no null.
• Use when you need sorted iteration or range queries (headMap, tailMap, subMap).

Page | 15
5.7 LinkedHashMap and LinkedHashSet
• LinkedHashMap = HashMap + doubly-linked list connecting entries in insertion order.
• Maintains insertion order (or access order if accessOrder=true in constructor).
• Useful for implementing LRU cache (accessOrder=true + override removeEldestEntry).
• Slightly slower than HashMap due to extra list maintenance.

5.8 Fail-fast vs Fail-safe Iterators

• Fail-fast: Iterator throws ConcurrentModificationException if collection is structurally modified


during iteration (outside iterator's own remove()). Detected via modCount.
• Fail-safe: Iterator works on a copy of collection. No exception, but may see stale data. Example:
CopyOnWriteArrayList, ConcurrentHashMap.

ArrayList<String> list = new ArrayList<>([Link]("a","b","c"));


for(String s : list) {
if([Link]("a")) [Link](s); // ConcurrentModificationException!
}

// Fix: use iterator's remove


Iterator<String> it = [Link]();
while([Link]()) { if([Link]().equals("a")) [Link](); }

5.9 Comparable vs Comparator

• Comparable: class implements Comparable<T>, overrides compareTo(T other). Natural ordering.


ONE ordering per class.
• Comparator: external object implementing compare(T o1, T o2). MULTIPLE orderings possible.
Pass to sort/TreeSet/TreeMap.

// Comparable — natural order


class Student implements Comparable<Student> {
public int compareTo(Student other) {
return [Link] - [Link]; // ascending by marks
}
}

// Comparator — custom order


Comparator<Student> byName = (a, b) -> [Link]([Link]);
[Link](byName);
// or: [Link]([Link](s -> [Link]));

⚠️ compareTo and compare must return: negative if first < second, 0 if equal, positive if first >
second. Getting this backwards causes wrong sort order.
⚠️ Never use a - b in comparator when a or b could be negative — integer overflow. Use
[Link](a, b).

Page | 16
6. Exception Handling — Full Hierarchy & Tricky Cases

6.1 Exception Hierarchy

Throwable
├─ Error (do NOT catch usually)
│ ├─ OutOfMemoryError
│ ├─ StackOverflowError
│ └─ VirtualMachineError
└─ Exception
├─ Checked Exceptions (must handle at compile time)
│ ├─ IOException
│ ├─ SQLException
│ └─ ClassNotFoundException
└─ RuntimeException (Unchecked — not required to handle)
├─ NullPointerException
├─ ArrayIndexOutOfBoundsException
├─ ClassCastException
├─ IllegalArgumentException
└─ NumberFormatException

6.2 Checked vs Unchecked

• Checked: must be either declared with 'throws' or caught. Compile error if ignored. Represent
recoverable conditions.
• Unchecked (RuntimeException): programmer errors — bugs in code. Not required to declare or
catch.
• Error: JVM-level problems. Generally not recoverable. Do not catch (except special cases like
OutOfMemoryError in monitoring).

6.3 try-catch-finally — Tricky Behaviours

try {
return 1;
} finally {
return 2; // finally OVERRIDES the try return!
}
// Result: 2. Finally always runs. Its return suppresses try's return.

⚠️ If both try and finally throw exceptions, the finally exception SUPPRESSES the try
exception. Try exception is lost.
⚠️ [Link]() in try block prevents finally from running — one of very few exceptions to
'finally always runs'.

6.4 try-with-resources (Java 7+)

Page | 17
try (Scanner sc = new Scanner([Link]);
FileReader fr = new FileReader("[Link]")) {
// use sc and fr
} // auto-close called on both in REVERSE order of declaration

• Resource must implement AutoCloseable (close() method).


• close() called even if exception thrown.
• If close() itself throws, and body also threw — close exception is suppressed. Access via
getSuppressed().

6.5 Multi-catch and Exception Chaining

// Multi-catch (Java 7+)


catch (IOException | SQLException e) { ... }
// e is effectively final — cannot reassign

// Exception chaining — preserve original cause


catch (IOException e) {
throw new RuntimeException("Failed to read", e); // e is the cause
}
// Retrieve cause: [Link]()

Page | 18
7. Generics — Type Erasure & Wildcards

7.1 Purpose and Basics


Generics provide compile-time type safety. The type parameter is replaced at compile time. At runtime, all
generics are erased — this is called type erasure.

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


[Link]("hello");
[Link](42); // compile error — caught at compile time
String s = [Link](0); // no cast needed — compiler inserts cast

7.2 Type Erasure

• At runtime, List<String> and List<Integer> are BOTH just List. Generic type info is erased.
• This is why you cannot do: new T[], instanceof List<String>, or get generic type at runtime
(without reflection tricks).

// At compile time:
List<String> list = new ArrayList<>();
// After erasure (what JVM sees):
List list = new ArrayList();

7.3 Wildcards

List<?> // unbounded — any type. Can only read as Object.


List<? extends Number> // upper bounded — Number or subtype. Read-only.
List<? super Integer> // lower bounded — Integer or supertype. Write
allowed.

• PECS Rule: Producer Extends, Consumer Super.


• If you are READING from a collection → use extends (upper bound).
• If you are WRITING to a collection → use super (lower bound).

// Producer (reading) — extends


void print(List<? extends Number> list) {
for(Number n : list) [Link](n); // reading OK
}

// Consumer (writing) — super


void addNumbers(List<? super Integer> list) {
[Link](1); [Link](2); // writing OK
}

7.4 Generic Methods

Page | 19
<T> T getFirst(List<T> list) { return [Link](0); }
<T extends Comparable<T>> T max(T a, T b) { return [Link](b) > 0 ?
a : b; }

Page | 20
8. Java 8+ — Lambda, Stream, Optional, Functional

8.1 Lambda Expressions

• Anonymous function — no name, can be assigned to functional interface reference.


• Syntax: (parameters) -> expression OR (parameters) -> { statements; }
Runnable r = () -> [Link]("Run");
Comparator<String> c = (a, b) -> [Link](b);
Function<Integer,Integer> sq = x -> x * x;

8.2 Functional Interfaces

Function<T,R> // T → R: apply(T t)
Predicate<T> // T → boolean: test(T t)
Consumer<T> // T → void: accept(T t)
Supplier<T> // () → T: get()
BiFunction<T,U,R> // T,U → R
UnaryOperator<T> // T → T
BinaryOperator<T> // T,T → T

8.3 Streams — Internal Pipeline

• Stream = sequence of elements supporting sequential and parallel aggregate operations.


• Three stages: Source → Intermediate operations (lazy) → Terminal operation (triggers execution).
• Intermediate: filter(), map(), flatMap(), sorted(), distinct(), limit(), skip() — LAZY, not executed
until terminal.
• Terminal: collect(), forEach(), reduce(), count(), findFirst(), anyMatch() — EAGER, triggers
pipeline.

List<String> result = [Link]()


.filter(s -> [Link]() > 3) // lazy
.map(String::toUpperCase) // lazy
.sorted() // lazy
.collect([Link]()); // terminal — executes all above

⚠️ Streams are single-use. Once a terminal operation runs, the stream is consumed. Reuse the
source collection.
⚠️ Do NOT modify the source collection inside stream operations —
ConcurrentModificationException.

8.4 Optional

• Container that may or may not hold a non-null value. Avoids NullPointerException by making null
handling explicit.
Optional<String> opt = [Link]("hello");

Page | 21
Optional<String> empty = [Link]();
Optional<String> maybe = [Link](possiblyNull);

[Link]() // returns value, throws if empty


[Link]("default") // returns value or default
[Link](() -> computeDefault())
[Link](() -> new RuntimeException())
[Link]() // check before get
[Link](String::length) // transform if present
[Link](s -> [Link]() > 3)

⚠️ Do NOT do [Link]() without isPresent() check — throws NoSuchElementException.


Use orElse() patterns instead.

Page | 22
9. Concurrency & Multithreading

9.1 Thread Creation — Three Ways

// Way 1: extend Thread


class MyThread extends Thread {
public void run() { [Link]("Running"); }
}
new MyThread().start();

// Way 2: implement Runnable (preferred — allows extending other class)


Runnable r = () -> [Link]("Running");
new Thread(r).start();

// Way 3: Callable + Future (can return value and throw checked


exceptions)
Callable<Integer> c = () -> 42;
ExecutorService ex = [Link]();
Future<Integer> future = [Link](c);
int result = [Link](); // blocks until result ready

⚠️ Always call start(), never run() directly. run() executes in the current thread, not a new one.

9.2 Thread Lifecycle

NEW → RUNNABLE → (BLOCKED/WAITING/TIMED_WAITING) → TERMINATED

• NEW: Thread created but start() not called.


• RUNNABLE: start() called. Eligible to run, scheduler decides when CPU is given.
• BLOCKED: Waiting to acquire an intrinsic lock (synchronized block held by another thread).
• WAITING: Waiting indefinitely ([Link](), [Link](), [Link]()).
• TIMED_WAITING: Waiting with timeout ([Link](n), [Link](n)).
• TERMINATED: run() completed or exception thrown.

9.3 synchronized — Intrinsic Locks

• Every Java object has an intrinsic lock (monitor). synchronized acquires it.
• Only one thread can hold an object's lock at a time.
// Synchronized method — locks 'this'
public synchronized void increment() { count++; }

// Synchronized block — more granular, better performance


public void increment() {
synchronized(this) { count++; }
}

Page | 23
// Static synchronized — locks the Class object
public static synchronized void staticMethod() { }

⚠️ Synchronizing on a non-final reference is dangerous — if the reference changes, two threads


can enter simultaneously.

9.4 volatile keyword in Java

• volatile variable: always read from and written to main memory, never from CPU cache.
• Ensures visibility: changes made by one thread are immediately visible to all other threads.
• Does NOT provide atomicity. x++ on a volatile int is NOT atomic (read-modify-write = 3 ops).
volatile boolean running = true;
// Thread A:
while(running) { doWork(); }
// Thread B:
running = false; // Thread A sees this because running is volatile

⚠️ volatile is for visibility, not mutual exclusion. For compound operations like count++, use
synchronized or AtomicInteger.

9.5 Race Condition and Deadlock

Race Condition
• When the outcome depends on the relative timing of multiple threads — undefined behaviour.
int count = 0;
// Thread 1 and Thread 2 both do: count++
// count++ is: read count, add 1, write back — three separate operations
// Result could be 1 instead of 2 if threads interleave on read/write

Deadlock
• Thread A holds lock X, waits for lock Y. Thread B holds lock Y, waits for lock X. Both blocked
forever.
// Prevention: always acquire locks in the same order
// Use tryLock() with timeout (ReentrantLock)
// Use concurrent data structures instead of explicit locking

9.6 ExecutorService and Thread Pools

• Creating threads manually is expensive. Thread pools reuse threads.


ExecutorService pool = [Link](4);
ExecutorService single = [Link]();
ExecutorService cached = [Link]();
ScheduledExecutorService sched = [Link](2);

[Link](() -> doWork()); // submit Runnable or Callable


[Link](); // no new tasks, finish existing

Page | 24
[Link](); // interrupt running tasks
[Link](5, [Link]);

9.7 [Link] — Key Classes

• AtomicInteger, AtomicLong, AtomicReference: lock-free thread-safe operations using CAS


(Compare-And-Swap).
• ReentrantLock: more flexible than synchronized. tryLock(), lockInterruptibly(), fairness policy.
• CountDownLatch: one or more threads wait until a set of operations complete. Not reusable.
• CyclicBarrier: all threads wait for each other at a barrier point. Reusable.
• Semaphore: controls access to a limited number of resources. acquire()/release().
• ConcurrentHashMap: thread-safe HashMap. Uses segment locking (Java 7) or CAS (Java 8+).
Much better than synchronized HashMap.
• CopyOnWriteArrayList: all writes create a fresh copy. Reads are non-blocking. Good for read-
heavy, rare-write scenarios.

9.8 The happens-before Relationship


• If action A happens-before action B, A's effects are visible to B.
• Rules: synchronized release happens-before subsequent acquire. volatile write happens-before
subsequent read. [Link]() happens-before any action in the started thread. Thread completion
happens-before [Link]() return.
• Without happens-before guarantee, compiler and CPU are free to reorder instructions.

Page | 25
10. Java Tricky Cases & Unexpected Behaviours

10.1 Integer Cache — == vs equals

Integer a = 100, b = 100;


a == b // TRUE — cached in range -128 to 127
Integer c = 200, d = 200;
c == d // FALSE — outside cache, different objects
⚠️ Always use .equals() for Integer comparison. The cache exists as an optimisation and creates
this trap.

10.2 Autoboxing and Unboxing Pitfalls

Integer x = null;
int y = x; // NullPointerException — unboxing null

Integer a = 1, b = 2;
boolean c = a < b; // unboxes a and b for comparison — OK
Integer sum = a + b; // unboxes, adds, autoboxes — OK but less efficient

⚠️ Unboxing null Integer, Long, Double etc. always throws NullPointerException.

10.3 String switch — Null handling

String s = null;
switch(s) { case "a": ... } // NullPointerException!
⚠️ switch on String does NOT handle null gracefully. Always null-check before switch on
String.

10.4 Array Covariance — Silent ClassCastException

Object[] objs = new String[3]; // compiles — arrays are covariant


objs[0] = 42; // ArrayStoreException at RUNTIME — not compile time

⚠️ Array covariance allows storing wrong type at runtime. Generic collections (List<String>)
are safer — compile-time check.

10.5 int division vs double

int a = 7, b = 2;
double d = a / b; // 3.0 — integer division first, then convert
double d2 = (double)a / b; // 3.5 — correct
double d3 = a / (double)b; // 3.5 — correct

Page | 26
10.6 char arithmetic

char c = 'A';
[Link](c + 1); // prints 66 (int arithmetic)
[Link]((char)(c+1)); // prints 'B'
[Link]("" + c + 1); // prints "A1" (String concat)

10.7 Default values

Instance variables (fields): int=0, long=0L, double=0.0, boolean=false,


char='\u0000', Object=null
Local variables: NO default — compile error if used before initialisation

10.8 String methods that return new String

String s = "hello";
[Link](); // returns NEW string — s unchanged!
[Link]("h","H"); // returns NEW string — s unchanged!
// Always: s = [Link]();
⚠️ String methods do NOT modify the original. You must assign the result. This is the most
common String bug after ==.

10.9 Overflow and Underflow

int max = Integer.MAX_VALUE; // 2147483647


[Link](max + 1); // -2147483648 — OVERFLOW, wraps around
// Java does NOT throw exception on int overflow — silent!
// Use long or [Link]() if overflow checking needed

10.10 ArrayList subList — shared backing

List<Integer> list = new ArrayList<>([Link](1,2,3,4,5));


List<Integer> sub = [Link](1, 3); // [2, 3]
[Link](0, 99);
[Link](list); // [1, 99, 3, 4, 5] — original changed!
⚠️ subList() returns a VIEW backed by the original. Modifications to subList affect the
original and vice versa.

Page | 27
11. Where Data Structures are Stored — Stack vs Heap

11.1 The Rule

✅ ALL objects (including arrays and collection objects) are ALWAYS on the HEAP. References to
them are on the STACK (or in Metaspace for static). Primitives are on the STACK if they are local
variables; inside objects, they are on the HEAP.

11.2 Examples

// Stack: reference variable 'list', Heap: ArrayList object and its


internal Node objects
ArrayList<String> list = new ArrayList<>();

// Stack: reference 'arr', Heap: the int[10] array object (even though
ints are primitive)
int[] arr = new int[10];

// Stack: 'x', Stack: 's' reference, Heap: String object (in pool)
int x = 5;
String s = "hello";

// Static field: in Metaspace (class data area), NOT stack or heap


static int counter = 0;

11.3 Collection Internal Storage

• ArrayList: Object[] array on heap. References in array point to element objects also on heap.
• LinkedList: Node objects on heap, each with data reference and next/prev references (also on heap).
• HashMap: Node[] table array on heap. Each Node object on heap. Key and value objects on heap.
• Stack ([Link]): extends Vector. Uses Object[] array on heap. Thread-safe but slow. Prefer
ArrayDeque.
• ArrayDeque: circular array on heap. No node overhead. Faster than LinkedList for stack/queue use.

11.4 ThreadLocal — Thread-Private Heap Storage

• ThreadLocal<T> provides a separate value for each thread accessing it.


• Internally: each Thread object has a ThreadLocalMap. ThreadLocal acts as key, stored value is
value.
• Values are on heap but accessible only by owning thread (until map entry is removed).
⚠️ Always call [Link]() when done — especially in thread pools. Otherwise
ThreadLocal values persist across task executions, causing memory leaks and data pollution.

Page | 28
12. Key Design Patterns — Interview Favourites

12.1 Singleton Pattern

// Thread-safe Singleton with double-checked locking


public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
synchronized([Link]) {
if(instance == null) { // second check after lock
instance = new Singleton();
}
}
}
return instance;
}
}

• volatile prevents reordering — ensures instance is fully constructed before reference is visible.
✅ Better alternative: Enum singleton — inherently thread-safe, handles serialization.
public enum Singleton { INSTANCE; }

12.2 Builder Pattern

Person person = new [Link]()


.name("Ram")
.age(25)
.email("ram@[Link]")
.build();

• Useful when object has many optional parameters. Avoids telescoping constructors.

12.3 Factory Method and Abstract Factory

// Factory Method
Shape shape = [Link]("circle"); // returns Circle

12.4 SOLID Principles — One-line Summaries

• S — Single Responsibility: a class should have one reason to change.


• O — Open/Closed: open for extension, closed for modification.
• L — Liskov Substitution: subclass should be usable wherever superclass is expected.
• I — Interface Segregation: clients should not depend on methods they don't use.
• D — Dependency Inversion: depend on abstractions, not concretions.

Page | 29
13. Key Comparison Questions — Interview Cheat Sheet

13.1 == vs equals()
== : reference equality (same memory address) for objects; value equality
for primitives
equals(): content equality — should be overridden in your classes
Rule: ALWAYS use equals() for objects. NEVER use == on String, Integer,
etc.

13.2 HashMap vs Hashtable vs ConcurrentHashMap


Feature HashMap Hashtable ConcurrentHashMap
Thread-safe? No Yes (all sync) Yes (fine-grained)
Null key? 1 allowed Not allowed Not allowed
Null value? Allowed Not allowed Not allowed
Performance Fast Slow (all sync) Best for concurrent
Legacy? No Yes (avoid) No (modern)

13.3 List vs Set vs Map


List: ordered, duplicates allowed, index-based access
Set: unordered (or sorted), NO duplicates
Map: key-value pairs, keys unique, values can duplicate

13.4 Checked vs Unchecked Exception


Checked: compile-time check. Must declare or catch. Recoverable
conditions.
Unchecked: runtime. Programming bugs. Not required to handle.

13.5 throw vs throws


throw: keyword used to explicitly throw an exception object: throw new
RuntimeException()
throws: keyword in method signature declaring potential exceptions: void
read() throws IOException

13.6 final vs finally vs finalize


final: modifier — variable (constant ref), method (no override), class
(no extend)
finally: block — always executes after try-catch (except [Link]() or
JVM crash)
finalize: method — called by GC before reclaiming. DEPRECATED (Java 9),
REMOVED (Java 18). Do not use.

13.7 abstract class vs interface


Abstract class: partial implementation, constructor, instance fields,
single inheritance

Page | 30
Interface: pure contract (Java 7-), default/static methods (Java 8+),
multiple

13.8 Runnable vs Callable


Runnable: run() — no return, no checked exception. Cannot get result.
Callable: call() — returns value, can throw checked exception. Use with
Future.

13.9 ArrayList vs LinkedList


ArrayList: fast get(i) O(1), slow insert/delete middle O(n), less memory
LinkedList: slow get(i) O(n), fast insert/delete at ends O(1), more
memory (pointers)

13.10 StringBuilder vs StringBuffer


StringBuilder: mutable, NOT thread-safe, FASTER — use in single-threaded
StringBuffer: mutable, thread-safe (synchronized), SLOWER — use in
multi-threaded

13.11 Iterator vs ListIterator


Iterator: forward only, works on any Collection, can remove
ListIterator: forward and backward, only for List, can add/set/remove, has
index

13.12 Comparable vs Comparator


Comparable: implemented by the class itself (intrinsic). compareTo(). One
natural order.
Comparator: external, can have multiple. compare(). Pass to sort methods.

Page | 31
14. Quick Revision — Common Interview Trap Summary

Traps to Memorise
⚠️ String: == compares references. Use .equals() always.
⚠️ Integer cache: == works for -128 to 127, fails outside.
⚠️ String methods return new String — must reassign.
⚠️ Scanner: nextInt() leaves \n — call nextLine() after to flush.
⚠️ ArrayList subList: is a view — changes propagate to original.
⚠️ HashMap key mutation after insertion: entry is lost.
⚠️ Don't override equals() without overriding hashCode() — breaks HashMap/HashSet.
⚠️ Overflow: int overflow is SILENT — wraps around without exception.
⚠️ Array covariance: Object[] arr = new String[3] compiles but runtime ArrayStoreException.
⚠️ Unboxing null: Integer x = null; int y = x; → NullPointerException.
⚠️ Static method 'override': hiding, not overriding. Resolved by reference type.
⚠️ [Link]() vs [Link](): run() runs in same thread, start() creates new thread.
⚠️ volatile is NOT atomic: x++ on volatile int is still 3 operations and not thread-safe.
⚠️ finalize() and [Link]() are hints only — not guaranteed to run when called.
⚠️ finally block: runs always EXCEPT [Link]() or JVM crash.
⚠️ subList, keySet, values, entrySet: all return VIEWS — backed by original collection.
⚠️ Streams are single-use and lazy — terminal operation triggers execution.
⚠️ switch on String: null causes NullPointerException — always null-check first.

Things that might look like errors but are valid Java
✅ String args[] and String[] args are identical in main method signature.
✅ Calling static method via object reference (e.g., [Link]()) compiles and runs.
✅ Integer.MAX_VALUE + 1 compiles and runs — returns Integer.MIN_VALUE (silent overflow).
✅ int[] arr = new int[-1]; compiles but throws NegativeArraySizeException at runtime.
✅ You can assign long to a variable declared as double without cast (widening).
✅ Casting from parent to child reference compiles, throws ClassCastException at runtime if wrong.
✅ Abstract class can have a constructor — called via super() from subclass.
✅ Interface can have a main() method — and can be run directly since Java 9.

End of Java Revision Notes — Review once a week for best retention.

Page | 32

You might also like