Java Complete Interview Notes
Java Complete Interview Notes
Notes
JVM Internals · OOP · Collections · Memory · Concurrency · Tricky Cases
Intermediate to Advanced — Weekly Revision Document
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.
1.3 What Happens When You Write 'public static void main(String[] args)'
• 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).
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).
Class Hierarchy
[Link]
implements Iterator<String>
implements Closeable
implements AutoCloseable
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.
Page | 4
2. Memory Architecture — Heap, Stack, Metaspace
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.
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.
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
GC Algorithms in Java
Serial GC (-XX:+UseSerialGC)
Single-threaded GC. Good for small apps, single-CPU. Stop-the-World for
all phases.
Shenandoah GC (-XX:+UseShenandoahGC)
Page | 6
Similar to ZGC — concurrent compaction. Low pause times.
⚠️ 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).
Page | 7
3. OOP — Concepts, Internals & Tricky Cases
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.
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
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
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.
⚠️ 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.
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.
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
• 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
• 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).
• 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.
⚠️ NEVER use == to compare String content. Always use .equals() or .equalsIgnoreCase(). This
is the #1 String bug in Java.
• 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.
Page | 12
// Good — one StringBuilder
StringBuilder sb = new StringBuilder();
for(int i=0;i<1000;i++) [Link](i);
String result = [Link]();
// 2. String + int
[Link]("Value: " + 1 + 2); // "Value: 12" (left to right)
[Link]("Value: " + (1+2)); // "Value: 3"
[Link](1 + 2 + " Value"); // "3 Value"
Page | 13
5. Collections Framework — Internals & Comparisons
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)
✅ 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.
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.
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.
⚠️ 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
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
• 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).
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'.
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
Page | 18
7. Generics — Type Erasure & Wildcards
• 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
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
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
⚠️ 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);
Page | 22
9. Concurrency & Multithreading
⚠️ Always call start(), never run() directly. run() executes in the current thread, not a new one.
• 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++; }
Page | 23
// Static synchronized — locks the Class object
public static synchronized void staticMethod() { }
• 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.
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
Page | 24
[Link](); // interrupt running tasks
[Link](5, [Link]);
Page | 25
10. Java Tricky Cases & Unexpected Behaviours
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
String s = null;
switch(s) { case "a": ... } // NullPointerException!
⚠️ switch on String does NOT handle null gracefully. Always null-check before switch on
String.
⚠️ Array covariance allows storing wrong type at runtime. Generic collections (List<String>)
are safer — compile-time check.
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)
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 ==.
Page | 27
11. Where Data Structures are Stored — Stack vs Heap
✅ 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 '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";
• 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.
Page | 28
12. Key Design Patterns — Interview Favourites
• 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; }
• Useful when object has many optional parameters. Avoids telescoping constructors.
// Factory Method
Shape shape = [Link]("circle"); // returns Circle
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.
Page | 30
Interface: pure contract (Java 7-), default/static methods (Java 8+),
multiple
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