Java Complete Handwritten Notes
Java Complete Handwritten Notes
JAVA
— complete notes —
Basics to Advanced
PART 2 · OOP
Classes & Objects 07
Constructors 08
The 4 Pillars — Overview 09
Encapsulation 10
Inheritance 11
Polymorphism 12
⚖ Abstract Classes vs Interfaces 13
static & final keywords 14
Access Modifiers & Packages 15
PART 4 · COLLECTIONS
Collections Framework — the Map 20
List: ArrayList vs LinkedList 21
Set: Hash / Linked / Tree 22
Map: HashMap / TreeMap / LinkedHashMap 23
Queue, Deque & Stack 24
Iterator, Comparable & Comparator 25
PART 5 · GENERICS
Generics 26
PART 7 · CONCURRENCY
Threads Basics 31
Synchronization & Locks 32
⚙ Executor Framework 33
PART 8 · ADVANCED
File I/O & NIO 34
Serialization 35
Serialization 35
Reflection 36
Annotations 37
Memory Management & GC 38
JDBC Basics 39
Design Patterns 40
✅ Best Practices & Interview Cheat-Sheet 41
PART 1 · FOUNDATIONS 01 / 41
☕What is Java?
Big picture
Java = platform-independent , object-oriented, compiled
+ interpreted language (Sun, 1995; now Oracle/OpenJDK).
Motto: "Write Once, Run Anywhere" (WORA) — code class name
compiles to bytecode , not machine code. MUST match file
Bytecode runs inside the JVM, so the *same .class file* name ([Link])!
runs on Windows/Linux/Mac.
Compilation flow
[Link] --(javac)--> [Link] (bytecode) --(java,
JVM)--> output
JVM has 3 jobs: Class-loader → Bytecode verifier →
Interpreter/JIT compiler
JIT (Just-In-Time) compiler converts hot bytecode →
native machine code at runtime for speed!
// example
public class Hello {
public static void main(String[] args) {
[Link]("Hello, World!");
}
}
✍ every app needs this exact main() signature — JVM's entry point
Type casting
Widening (implicit/safe): int → long → float → double
Narrowing (explicit, may lose data): double d=9.7; int i=
(int)d; // i=9
// example
int score = 95;
double gpa = 8.9;
char grade = class="c-string">'A';
final double PI = 3.14159; class="c-comment">// constant
var city = "Pune"; class="c-comment">// inferred
as String
➕Operators
Categories
Arithmetic: + - * / % (modulo = remainder)
Relational: == != > < >= <= → returns boolean
Logical: && (AND) || (OR) ! (NOT) — these are short- == vs .equals()
circuit (2nd operand skipped if not needed) is the #1 Java
Bitwise: & | ^ ~ << >> >>> (>>> = unsigned right interview trap!
shift, fills with 0)
Assignment: = += -= *= /= %=
Ternary: condition ? valueIfTrue : valueIfFalse
Gotchas
== on objects/Strings compares *references* not
content → use .equals() for value comparison!
int / int = int (truncates!) → 7/2 = 3, not 3.5. Cast one
operand: (double)7/2 = 3.5
% works on doubles too: 7.5 % 2 = 1.5
// example
int a = 10, b = 3;
[Link](a / b); class="c-comment">// 3
(int division)
[Link](a % b); class="c-comment">// 1
[Link]((double)a/b);class="c
-comment">//
3.333...
// example
class="c-comment">// classic switch (watch the fall-
through!)
int day = 3;
switch (day) {
case 1: [Link]("Mon"); break;
case 2: [Link]("Tue"); break;
case 3: [Link]("Wed"); break;
default: [Link]("?");
}
Loops
The 4 loop forms
for (init; condition; update) → use when iteration count is
known
while (condition) → checks *before* each run, may run 0 for-each can't
times modify the index or
do-while (condition) → checks *after*, guaranteed to run go backwards —
at least once use classic for then
enhanced for / for-each: for(Type item : collection) →
clean iteration, no index, read-only
Loop control
break → exits the loop entirely
continue → skips to next iteration
labeled break/continue → break out of *outer* loop from
inside a nested loop: `outer: for(...) { break outer; }`
// example
for (int i = 0; i < 5; i++) {
if (i == 3) continue; class="c-comment">// skip 3
[Link](i);
}
outer:
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++) {
if (j == 1) break outer; class="c-comment">//
kills BOTH loops
}
Arrays
Basics
Fixed-size, same-type, zero-indexed container. Declared
as: int[] arr; or int arr[];
[Link] → property (no parens!) gives size. Default array size can
values: 0 for numbers, false, null for objects. NEVER change
Arrays are objects on the heap even for primitives — arr once created —
itself is a reference. that's what List is
for!
Multi-dimensional
int[][] grid = new int[3][4]; → 3 rows, 4 cols ("array of
arrays")
Can be *jagged* (ragged): rows of different lengths — new
int[3][]; then assign each row separately.
// example
int[] scores = {90, 85, 77, 92};
class="c-
[Link]([Link]);
comment">// 4
6); class="c-
int[] copy = [Link](scores,
comment">// pads with 0s
class="c-
[Link](scores);
comment">// in-place ascending
// example
class Car {
String model; class="c-comment">// instance field
static int count; class="c-comment">// shared across
ALL Car objects
Car(String model) {
[Link] = model; class="c-comment">//
[Link] = param
count++;
}
void drive() { [Link](model + " driving
");
}
}
Constructors
Rules
Same name as class, NO return type (not even void).
If you write zero constructors, Java auto-generates a no-
arg default constructor. super(...) if
The moment you write ANY constructor, the free default used, MUST be the
one disappears! very first statement
Constructor overloading: multiple constructors,
different parameter lists.
Constructor chaining: this(...) calls another constructor
in same class; super(...) calls parent's constructor.
// example
class Point {
int x, y;
Point() { this(0, 0); } class="c-
comment">// chains to below
Point(int x, int y) {
this.x = x; this.y = y;
}
}
Inheritance
"is-a" relationship. Child class reuses/extends parent's
fields+methods via extends.
Polymorphism
"many forms" — same method name behaves differently.
Overloading (compile-time) vs Overriding (runtime).
Encapsulation
How to do it
Mark fields private. Expose controlled access via public
getters/setters.
Lets you validate input, make fields read-only (getter without
only), or change internal representation later without encapsulation,
breaking callers. anyone could set
balance = -9999
// example directly!
class BankAccount {
private double balance; class="c-comment">// hidden!
Inheritance
Mechanics
class Child extends Parent { } → Child inherits
public/protected members.
Java supports single inheritance only for classes (no reference TYPE
multiple class inheritance — avoids Diamond Problem). decides what you
But a class CAN implement multiple interfaces. can CALL; object
protected = visible to subclasses + same package. TYPE decides
private members are NOT inherited/visible. what runs
// example
class Animal {
protected String name;
void eat() { [Link](name
+ " eats"); }
}
Polymorphism
Compile-time (Overloading)
Same method name, different parameter list
(type/number/order). Resolved at COMPILE time.
Return type ALONE cannot differentiate overloads. overloading =
same class,
Runtime (Overriding) different args |
Subclass provides specific implementation of a parent overriding =
method — same signature. parent-child, same
Resolved at RUNTIME based on the actual object (this is signature
"dynamic method dispatch").
Rules: same name+params, return type same/covariant,
access modifier same-or-wider, can't override
static/final/private.
// example
class MathUtil {
int add(int a, int b) { return a + b; }
class="c-comment">// overload 1
double add(double a, double b) { return a + b; }
class="c-comment">// overload 2
int add(int a, int b, int c) { return a+b+c; }
class="c-comment">// overload 3
}
interface
Pure contract — historically only abstract methods; Java
8+ allows default and static methods too.
Fields are implicitly public static final (constants only).
A class can implement multiple interfaces → workaround
for no multiple inheritance!
Use for "can-do" capability (Runnable, Comparable,
Serializable).
// example
interface Flyable {
void fly(); class="c-comment">//
abstract
default void land() { class="c-comment">//
Java 8+ default method
[Link]("landing...");
}
}
interface Swimmable { void swim(); }
// example
class Config {
static int counter;
static final String VERSION; class="c-comment">//
constant, set once
static { class="c-comment">//
static initializer block
VERSION = "1.0.0";
d");
[Link]("Config class loade
}
}
Common methods
length(), charAt(i), substring(start,end), indexOf(),
toUpperCase(), trim(), split(regex), replace()
StringBuilder → mutable! Use in loops for heavy
concatenation — way faster than String += in a loop.
StringBuilder methods: append(), insert(), reverse(),
delete(), toString()
// example
String a = "cat";
String b = "cat";
[Link](a == b); class="c-
comment">// true (pool, same ref)
// example
Integer i1 = 100, i2 = 100;
[Link](i1 == i2); class="c-
comment">// true (cached)
Exception Handling
Hierarchy
Throwable → Error (JVM-level, e.g. OutOfMemoryError,
don't catch) + Exception
Exception splits into: Checked (must handle/declare, e.g. never leave an
IOException — compiler enforced) and empty catch block!
Unchecked/RuntimeException (NullPointerException, ("swallowing"
ArrayIndexOutOfBounds — not enforced). exceptions = silent
bugs)
try / catch / finally
finally block ALWAYS runs (even after return!) — except
[Link]() or JVM crash.
Can catch multiple types: catch (IOException |
SQLException e)
try-with-resources → auto-closes anything implementing
AutoCloseable (streams, files, connections).
throw = actually raise an exception. throws = declare in
method signature that it might happen.
Custom exceptions: extend Exception (checked) or
RuntimeException (unchecked).
// example
try {
int[] arr = new int[3];
[Link](arr[5]); class="c-
comment">// throws ArrayIndexOOB
} catch (ArrayIndexOutOfBoundsException e) {
[Link]("Caught: " + [Link]());
} finally {
[Link]("always runs");
}
// example
enum Day {
MON("Monday"), TUE("Tuesday"), WED("Wedne
sday");
private final String full;
Day(String full) { [Link] = full;
} class="c-
comment">// enum constructor!
String getFull() { return full; }
}
Day d = [Link];
[Link]([Link]() + " "
+ [Link]());
class="c-comment">// Monday 0
Choosing wisely
Need fast index lookup + duplicates? → ArrayList
Need fast insert/delete at ends? → LinkedList / ArrayDeque
Need uniqueness + fast lookup? → HashSet
Need sorted order automatically? → TreeSet / TreeMap
Need insertion order preserved? → LinkedHashSet /
LinkedHashMap
// example
List<String> list = new ArrayList<>();
[Link]("a"); [Link]("b"); [Link](1, "x"); class="c-
comment">// insert at index
[Link]("a"); class="c-comment">// remove by
VALUE
[Link](0); class="c-comment">// remove by
INDEX (int overload!)
[Link](list); class="c-comment">// [x, b]
[Link](list);
[Link](list);
boolean has = [Link]("b");
// example
Set<String> hs = new HashSet<>();
[Link]("apple");
[Link]("banana"); [Link]("apple");
class="c-comment">// dup ignored
class="c-comment">// 2
[Link]([Link]());
needs hashCode+equals
class="c-comment">// custom object
overridden:
class Point {
int x, y;
ect o) { /* compare
@Override public boolean equals(Obj
x,y */ return true; }
n
@Override public int hashCode() { retur
[Link](x, y); }
}
Variants
LinkedHashMap → preserves insertion order (or access
order — great for LRU cache!).
TreeMap → keys sorted automatically, O(log n),
implements NavigableMap (floorKey, ceilingKey etc).
Key methods
put, get, getOrDefault(k, default), containsKey, remove,
keySet(), values(), entrySet()
merge/compute/computeIfAbsent → powerful for
counters & grouping in one line!
// example
Map<String, Integer> wordCount = new HashMap<>();
String[] words = {"a","b","a","c","a"};
for (String w : words)
[Link](w, 1, Integer::sum); class="c-
comment">// increments count!
[Link](wordCount); class="c-comment">// {a=3,
b=1, c=1}
PriorityQueue
Min-heap by default (smallest first) — O(log n)
insert/remove. Pass a Comparator for max-heap or custom
order.
// example
Deque<Integer> stack = new ArrayDeque<>();
[Link](1); [Link](2); [Link](3);
[Link]([Link]()); class="c-comment">// 3
(LIFO)
e<>
PriorityQueue<Integer> pq = new PriorityQueu
([Link]());
[Link]([Link](5,1,9,3));
[Link]([Link]()); class="c-comment">// 9
(max-heap here)
// example
List<Integer> nums = new ArrayList<>([Link](1,2,3,4,5));
Iterator<Integer> it = [Link]();
while ([Link]()) {
if ([Link]() % 2 == 0) [Link](); class="c-comment">// SAFE removal
}
Generics
Why generics
Compile-time type safety + no manual casting.
List<String> guarantees only Strings go in — caught at
compile time, not runtime.
generics are
Pre-generics (Java 1.4-) you'd store Object and cast ERASED at
everywhere → ClassCastException risk. runtime ("type
erasure") —
Generic classes & methods
List<String> and
class Box<T> { T value; } → T is a type placeholder,
List<Integer> are
replaced at usage: Box<String>
same .class!
Generic method: static <T> T firstElement(List<T> list) {
return [Link](0); }
Multiple params: class Pair<K, V> { K key; V value; }
// example
class Box<T> {
private T value;
void set(T value) { [Link] = value; }
T get() { return value; }
}
Box<Integer> b = new Box<>();
[Link](42); class="c-comment">// only ints allowed
- compile time checked!
λLambda Expressions
Syntax
(parameters) -> expression OR (parameters) -> {
statements; return x; }
Only works where a functional interface (exactly ONE lambda body
abstract method) is expected. can't reassign a
Types are usually inferred; can omit parens for single variable from the
param: x -> x*2 enclosing scope!
Why
Replaces verbose anonymous classes for simple behavior-
passing — huge boilerplate reduction.
Enables treating behavior as data — pass functions as
arguments.
Lambdas can capture outer variables, but they must be
effectively final (never reassigned after).
// example
class="c-comment">// old way: anonymous
class
Comparator<String> byLength = new Compa
rator<String>() {
public int compare(String a, String b)
{ return
[Link]() - [Link](); }
};
Functional Interfaces
Built-in ones ([Link])
Function<T,R> → T apply(T)→R (transform)
Predicate<T> → boolean test(T) (filter/condition)
Consumer<T> → void accept(T) (do something, no method
return) reference forms:
Supplier<T> → T get() (produce a value, no input) Class::static,
obj::instance,
BiFunction<T,U,R>, UnaryOperator<T>,
Class::instance,
BinaryOperator<T> → specialized variants
Class::new
@FunctionalInterface
Annotation → marks an interface as having exactly 1
abstract method; compiler enforces this.
Method references are shorthand lambdas:
ClassName::methodName, instance::methodName,
ClassName::new
// example
Predicate<Integer> isEven = n -> n % 2 == 0;
[Link]([Link](4)); class="c-
comment">// true
@FunctionalInterface
interface Calculator { int calc(int a, int b); }
Calculator add = (a, b) -> a + b;
Streams API
Pipeline structure
Source (collection/array) → intermediate ops (lazy,
chainable) → terminal op (triggers execution).
Intermediate: filter(), map(), sorted(), distinct(), limit(), streams are
skip(), flatMap() declarative ("what")
Terminal: collect(), forEach(), count(), reduce(), not imperative
anyMatch(), min()/max(), toArray() ("how") — great for
Streams don't run until a terminal op is called (lazy readability
evaluation), and can only be consumed ONCE.
Collectors
[Link](), toSet(), toMap(k,v), joining(", "),
groupingBy(fn), counting(), summingInt(fn)
// example
List<String> names =
[Link]("Amit","Riya","Zoe","Aman","Bob");
❓Optional
Purpose
A container that may or may not hold a value — makes
"absence" explicit instead of returning null everywhere.
Goal: reduce NullPointerExceptions by forcing callers to don't call .get()
handle the empty case. without checking
isPresent() first —
Core methods
defeats the whole
[Link](x) (non-null), [Link](x) (may be
purpose!
null), [Link]()
isPresent()/isEmpty(), get() (risky, throws if empty!),
orElse(default), orElseGet(supplier), orElseThrow()
ifPresent(consumer), map(fn), filter(predicate) —
chainable, stream-like!
// example
Optional<String> findUser(int id) {
:
return id == 1 ? [Link]("Alice")
[Link]();
}
.println("Found: " +
findUser(1).ifPresent(n -> [Link]
n));
Threads Basics
Creating threads
Extend Thread and override run() — OR implement
Runnable and pass to `new Thread(runnable)`
(preferred! keeps single inheritance free).
calling run()
Call .start() to actually spawn a new thread — calling instead of start() is
.run() directly just runs it on the current thread (common a top beginner
mistake!).
mistake — runs on
SAME thread!
Lifecycle
NEW → RUNNABLE → (BLOCKED/WAITING/TIMED_WAITING)
→ TERMINATED
join() → caller thread waits for this thread to finish.
sleep(ms) → pauses current thread, doesn't release locks
it holds.
// example
class MyTask implements Runnable {
public void run() { [Link]("Running
in: " +
[Link]().getName()); }
}
// example
class Counter {
private int count = 0;
+; }
public synchronized void increment() { count+
class="c-comment">// atomic now
}
public synchronized int get() { return count;
}
⚙Executor Framework
Why not raw threads
Creating a new Thread per task is expensive &
unmanaged. ExecutorService = a managed thread pool.
[Link](n), forgetting
newCachedThreadPool(), newSingleThreadExecutor(), [Link]() =
newScheduledThreadPool(n)
classic reason a
Java app "hangs"
Submitting work
and won't exit
execute(Runnable) → fire and forget. submit(Callable<T>)
→ returns a Future<T> you can .get() a result from
(blocks until done).
Callable is like Runnable but CAN return a value + throw
checked exceptions.
Always call shutdown() when done, or the pool's threads
keep the JVM alive forever!
// example
ExecutorService pool = [Link](4);
[Link]([Link]()); class="c-comment">//
blocks until 42 is ready
[Link](); class="c-comment">//
don't forget!
// example
class="c-comment">// modern NIO way - very clean:
Path path = [Link]("[Link]");
List<String> lines = [Link](path);
[Link](path, "new content",
[Link]);
Serialization
Concept
Converting an object's state → byte stream (to save to
disk / send over network). Deserialization = reverse.
Class must implement the marker interface Serializable modern apps
(no methods to implement!). often use JSON
serialVersionUID → version ID; mismatch during (Jackson/Gson)
deserialize throws InvalidClassException. instead of native
transient keyword → field is SKIPPED during serialization Java serialization
(e.g. passwords, caches).
// example
class User implements Serializable {
private static final long serialVersionUI
D = 1L;
String name;
transient String password; class="c-comment">// NOT
serialized!
}
class="c-comment">// writing
try (ObjectOutputStream out = new Objec
tOutputStream(new
FileOutputStream("[Link]"))) {
[Link](new User());
}
class="c-comment">// reading
try (ObjectInputStream in = new Objec
tInputStream(new
FileInputStream("[Link]"))) {
User u = (User) [Link]();
}
Reflection
What it does
Inspect/manipulate classes, methods, fields at runtime —
even private ones!
Powers frameworks like Spring, Hibernate, JUnit (e.g. reflection can
reading @annotations, calling methods dynamically). access private
Entry point: [Link](), or [Link], or members - powerful
[Link]("[Link]") but breaks
encapsulation, use
Caution carefully
Slower than direct calls, bypasses compile-time type
safety, can break encapsulation (setAccessible(true)).
Use sparingly in application code — great for
tools/frameworks, risky for everyday business logic.
// example
Class<?> cls = [Link]("[Link]");
[Link]([Link]()); class="c-
comment">// String
Annotations
Built-in ones
@Override, @Deprecated,
@SuppressWarnings("unchecked"), @FunctionalInterface,
@SafeVarargs
this is exactly
Creating your own how JUnit's @Test
annotation works
@interface MyAnnotation { String value(); int priority()
default 1; }
under the hood!
// example
@Retention([Link])
@Target([Link])
@interface Test {
String description() default "";
}
class Calc {
@Test(description = "checks addition")
void testAdd() { /* ... */ }
}
GC basics
GC uses reachability: an object with no live references is
eligible for collection.
You cannot force GC — [Link]() is only a
*request/hint*, JVM may ignore it.
Common GC algorithms: Serial, Parallel, G1 (default
since Java 9), ZGC/Shenandoah (low-pause, for huge
heaps).
JDBC Basics
Core objects
[Link](url, user, pass) →
opens a Connection to the DB.
Statement → run static SQL. PreparedStatement → "SELECT *
precompiled + parameterized, prevents SQL injection , FROM users
faster for repeated runs. WHERE id=" +
ResultSet → cursor over query results, .next() moves userInput -> classic
forward, .getString/getInt(col) reads values. SQL injection
vulnerability!
Best practice
ALWAYS use PreparedStatement with ? placeholders for
any user input — never string-concatenate SQL!
Use try-with-resources for
Connection/Statement/ResultSet — they all implement
AutoCloseable.
// example
String url = "jdbc:mysql:class="c-
comment">//localhost:3306/mydb";
try (Connection conn = [Link](
url,
"user", "pass");
PreparedStatement ps = [Link](
"SELECT name FROM users WHERE id = ?")) {
[Link](1, 42);
try (ResultSet rs = [Link]()) {
while ([Link]()) {
[Link]([Link]("name"));
}
}
} catch (SQLException e) { [Link]();
}
Design Patterns
Singleton
Only ONE instance ever exists globally. Private constructor
+ static getInstance().
Best modern approach: enum singleton (thread-safe, GoF catalogued
serialization-safe, one line!). 23 patterns across
Creational /
Factory Structural /
Encapsulate object creation logic → caller doesn't need to Behavioral —
know the concrete class, just the interface.
these 4 come up
most
Builder
For objects with MANY optional params — avoids
telescoping constructors, chainable & readable.
Observer
One-to-many dependency: when subject changes state, all
registered observers get notified automatically (pub-sub
base).
// example
d-safe, best
class="c-comment">// Singleton via enum (threa
practice)
enum AppConfig {
INSTANCE;
private String setting = "default";
public String get() { return setting; }
}
[Link]();
✅
Best Practices & Interview Cheat-Sheet
Code quality
Favor composition over inheritance when possible —
more flexible, less fragile.
Program to an interface, not implementation: List<T> l = you made it
new ArrayList<>(); through advanced
Make classes immutable where possible (final fields, no Java — now go
setters) — thread-safe by default, easier to reason about. build something!
Always override equals() + hashCode() together, and
toString() for debugging.