📑 Table of Contents
- Java Fundamentals
- OOPs Concepts
- SOLID Principles
- Design Patterns
- Exception Handling
- Collections Framework
- Multithreading
- Interview Questions & Important Points
1. Java Fundamentals
1.1 Key Features of Java
- Platform Independent - WORA (Write Once Run Anywhere) - JVM is platform dependent
- Object-Oriented - Everything is object
- Robust - Strong memory management
- Secure - No explicit pointers
- Multithreaded - Supports concurrent programming
- Portable - Same bytecode runs everywhere
1.2 Basic Terminology
JVM, JRE, JDK
- JVM (Java Virtual Machine) - Executes bytecode
- JRE (Java Runtime Environment) = JVM + Libraries
- JDK (Java Development Kit) = JRE + Development Tools
A compiler converts the whole program into machine code (or bytecode) before execution, while an interpreter translates and executes the program line by line.
Identifiers & Keywords
// Legal Identifiers int age; // ✓ String name123; // ✓ double _salary; // ✓ float $amount; // ✓ // Illegal Identifiers int 123age; // ✗ Can't start with number String class; // ✗ Keyword float my-name; // ✗ No hyphen // 50 Reserved Keywords // abstract, class, interface, extends, implements // public, private, protected, static, final // if, else, switch, case, break, continue // try, catch, finally, throw, throws // etc.
Data Types
// Primitive Types (8 types) byte b = 127; // 1 byte short s = 32767; // 2 bytes int i = 2147483647; // 4 bytes long l = 9223372036854775807L; // 8 bytes float f = 3.14f; // 4 bytes double d = 3.14159; // 8 bytes char c = 'A'; // 2 bytes boolean bool = true; // 1 bit // Reference Types String str = "Hello"; int[] arr = {1, 2, 3}; Object obj = new Object();
Type Casting
// Implicit (Widening) - Automatic byte → short → int → long → float → double // Explicit (Narrowing) - Manual double d = 10.5; int i = (int) d; // 10 (loss of data)
1.3 Operators
// Arithmetic: + - * / % // Relational: == != > < >= <= // Logical: && || ! // Bitwise: & | ^ ~ << >> // Assignment: = += -= *= /= %= // Ternary Operator int age = 18; String status = (age >= 18) ? "Adult" : "Minor";
1.4 Control Statements
If-Else
if (condition) { // code } else if (condition) { // code } else { // code }
Switch
int day = 3; switch(day) { case 1: System.out.println("Monday"); break; case 2: System.out.println("Tuesday"); break; case 3: System.out.println("Wednesday"); break; default: System.out.println("Invalid"); }
Loops
// For Loop for (int i = 0; i < 5; i++) { System.out.println(i); } // Enhanced For Loop int[] arr = {1, 2, 3}; for (int num : arr) { System.out.println(num); } // While Loop int i = 0; while (i < 5) { System.out.println(i); i++; } // Do-While do { System.out.println(i); i++; } while (i < 5);
Procedural programming organizes code around functions, while object-oriented programming organizes code around objects that contain both data and methods.
2. OOPs Concepts
2.1 Four Pillars of OOPs
1. Encapsulation
Theory: Wrapping data and methods into a single unit, hiding internal details.
Key Points:
- Data hiding through private access modifier
- Public getters/setters for controlled access
- Improves maintainability and flexibility
Example:
public class BankAccount { // Private fields private String accountNumber; private double balance; // Constructor public BankAccount(String accountNumber) { this.accountNumber = accountNumber; this.balance = 0.0; } // Public methods (getters and setters) public double getBalance() { return balance; } public void deposit(double amount) { if (amount > 0) { balance += amount; } } public void withdraw(double amount) { if (amount > 0 && amount <= balance) { balance -= amount; } } }
2. Inheritance
Theory: Creating new classes (child) from existing classes (parent).
Key Points:
- Code reusability
extendskeyword- Single inheritance in Java
superkeyword to access parent
Example:
// Parent class class Animal { String name; Animal(String name) { this.name = name; } void eat() { System.out.println(name + " is eating"); } void sleep() { System.out.println(name + " is sleeping"); } } // Child class class Dog extends Animal { String breed; Dog(String name, String breed) { super(name); // Calling parent constructor this.breed = breed; } // Method Overriding @Override void eat() { System.out.println(name + " (Dog) is eating pedigree"); } // Child-specific method void bark() { System.out.println(name + " is barking"); } }
3. Polymorphism
Theory: Ability to take many forms.
Types:
- Compile-time (Method Overloading) - Same method name, different parameters
- Runtime (Method Overriding) - Child class redefines parent method
Example:
// Method Overloading (Compile-time) class Calculator { int add(int a, int b) { return a + b; } int add(int a, int b, int c) { return a + b + c; } double add(double a, double b) { return a + b; } } // Method Overriding (Runtime) class Vehicle { void start() { System.out.println("Vehicle starting..."); } } class Car extends Vehicle { @Override void start() { System.out.println("Car starting with key..."); } } class Motorcycle extends Vehicle { @Override void start() { System.out.println("Motorcycle kick-starting..."); } } // Polymorphic behavior Vehicle v1 = new Car(); Vehicle v2 = new Motorcycle(); v1.start(); // Car starting with key... v2.start(); // Motorcycle kick-starting...
Vehicle v = new Car(); means the reference is of the parent class, but the actual object is of the child class. You can access only the methods defined in Vehicle, but if those methods are overridden, the Car implementation runs at runtime. Car c = new Car(); allows access to both inherited and Car-specific methods.
4. Abstraction
Theory: Hiding implementation details and showing only essential features.
Key Points:
- Abstract classes (0-100% abstraction)
- Interfaces (100% abstraction)
abstractkeyword
Example:
// Abstract Class abstract class Shape { String color; Shape(String color) { this.color = color; } // Abstract method abstract double area(); // Concrete method void display() { System.out.println("Color: " + color); System.out.println("Area: " + area()); } } // Concrete class class Circle extends Shape { double radius; Circle(String color, double radius) { super(color); this.radius = radius; } @Override double area() { return Math.PI * radius * radius; } } // Interface (100% abstraction) interface Drawable { void draw(); // Abstract by default default void print() { // Java 8+ System.out.println("Printing..."); } static void show() { // Java 8+ System.out.println("Showing..."); } } // Implementing interface class Rectangle extends Shape implements Drawable { double length, width; Rectangle(String color, double length, double width) { super(color); this.length = length; this.width = width; } @Override double area() { return length * width; } @Override public void draw() { System.out.println("Drawing Rectangle"); } }
- Abstraction: Hides unnecessary implementation details and exposes only the essential functionality.
- Polymorphism: Allows the same method or interface to have different behaviors depending on the object, making code flexible and reusable.
2.2 Important OOPs Keywords
this Keyword
class Student { String name; int age; Student(String name, int age) { this.name = name; // Referring to instance variable this.age = age; } void display() { System.out.println(this.name); // Implicit } }
super Keyword
class Parent { String value = "Parent"; void show() { System.out.println("Parent method"); } } class Child extends Parent { String value = "Child"; void show() { super.show(); // Calling parent method System.out.println(super.value); // Access parent variable System.out.println(this.value); // Access child variable } }
final Keyword
// For class - Cannot be inherited final class FinalClass { } // For method - Cannot be overridden class Parent { final void finalMethod() { } } // For variable - Cannot be changed (constant) final int MAX_VALUE = 100;
static Keyword
class Counter { static int count = 0; // Shared across all instances int instanceVar = 0; static void staticMethod() { System.out.println("Static method"); // Can't access instance variables directly } void instanceMethod() { System.out.println("Instance method"); System.out.println(count); // Can access static } } // Usage Counter.staticMethod(); // Call without object System.out.println(Counter.count);
3. SOLID Principles
SOLID is an acronym for five design principles that help developers create maintainable, scalable, and robust object-oriented software.
S - Single Responsibility Principle (SRP)
Theory: A class should have only one reason to change. - Each class should have exactly one responsibility or job.
- Single Job: A class should do only one thing
- One Reason to Change: If you need to modify the class, there should be only one reason (its single responsibility)
- Separation of Concerns: Different concerns should be in different classes
// ❌ Bad - Multiple responsibilities class Employee { void calculateSalary() { } void saveToDatabase() { } void generateReport() { } } // ✅ Good - Single responsibility each class SalaryCalculator { void calculateSalary(Employee e) { } } class EmployeeRepository { void save(Employee e) { } } class ReportGenerator { void generate(Employee e) { } }
O - Open/Closed Principle (OCP)
Theory: Open for extension, closed for modification. Software entities (classes, modules, functions) should be open for extension but closed for modification
- Open for Extension: You can add new functionality
- Closed for Modification: You don't change existing code
- Add new features by extending, not by modifying existing code
// ❌ BAD - Violates OCP (Must modify for new shapes) class AreaCalculator { public double calculateArea(Object shape) { // ❌ Need to modify for EVERY new shape if (shape instanceof Rectangle) { Rectangle r = (Rectangle) shape; return r.width * r.height; } else if (shape instanceof Circle) { Circle c = (Circle) shape; return Math.PI * c.radius * c.radius; } // ❌ Must add else-if for Triangle else if (shape instanceof Triangle) { Triangle t = (Triangle) shape; return 0.5 * t.base * t.height; } return 0; } } // ✅ GOOD - Follows OCP (Extend without modifying) // Abstraction - Open for extension interface Shape { double calculateArea(); // Closed for modification } // Implementations - Can add as many as needed class Rectangle implements Shape { private double width, height; Rectangle(double width, double height) { this.width = width; this.height = height; } @Override public double calculateArea() { return width * height; } } class Circle implements Shape { private double radius; Circle(double radius) { this.radius = radius; } @Override public double calculateArea() { return Math.PI * radius * radius; } } // ✅ NEW Shape - No existing code modified! class Triangle implements Shape { private double base, height; Triangle(double base, double height) { this.base = base; this.height = height; } @Override public double calculateArea() { return 0.5 * base * height; } } // AreaCalculator - Closed for modification class AreaCalculator { public double calculateTotalArea(Shape[] shapes) { double total = 0; for (Shape shape : shapes) { total += shape.calculateArea(); // Works with ANY shape } return total; } } // Usage public class OCPDemo { public static void main(String[] args) { Shape[] shapes = { new Rectangle(10, 20), new Circle(7), new Triangle(8, 10) // New shape works without changes! ✅ }; AreaCalculator calculator = new AreaCalculator(); System.out.println("Total Area: " + calculator.calculateTotalArea(shapes)); } }
L - Liskov Substitution Principle (LSP)
Theory: Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.
- Child classes should be substitutable for parent classes
- A program using parent class should work correctly when using child class
- Child should not change the behavior expected from parent
Liskov Substitution Principle states that a child class should be replaceable for its parent class without changing the program's behavior. The
Rectangle–Squareexample violates LSP because a square cannot behave exactly like a general rectangle. Similarly,Ostrichshould not extendBirdifBirdguarantees afly()method, since replacing aBirdwith anOstrichbreaks the expected behavior.
// ❌ Bad class Bird { void fly() { System.out.println("Bird is flying"); } } class Ostrich extends Bird { @Override void fly() { throw new UnsupportedOperationException("Ostrich can't fly"); } } public class Main { public static void main(String[] args) { Bird bird = new Ostrich(); bird.fly(); // ❌ Runtime Error } } // ✅ Good class Bird { void eat() { System.out.println("Bird is eating"); } } class Sparrow extends Bird { @Override void eat() { System.out.println("Sparrow is eating"); } } public class Main { public static void main(String[] args) { Bird bird = new Sparrow(); bird.eat(); // ✅ Works perfectly } }
I - Interface Segregation Principle (ISP)
Theory: Clients should not be forced to depend on interfaces they do not use.
- Don't create fat/bloated interfaces
- Split large interfaces into smaller, focused ones
- Classes should only implement what they need
// ❌ Bad - Fat interface interface Worker { void work(); void eat(); void sleep(); } // ✅ Good - Segregated interfaces interface Workable { void work(); } interface Eatable { void eat(); } interface Sleepable { void sleep(); } class Developer implements Workable, Eatable { public void work() { } public void eat() { } // Not forced to implement sleep }
D - Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.
- High-level modules (business logic) → Don't depend on low-level modules (database, API)
- Both depend on interfaces/abstractions
- Concrete implementations (details) implement the abstractions
Dependency Inversion Principle means high-level classes should depend on interfaces (abstractions) rather than concrete classes, making the code flexible, loosely coupled, and easy to extend.
class Keyboard { void type() { System.out.println("Typing..."); } } class Computer { private Keyboard keyboard = new Keyboard(); // ❌ Depends on concrete class void work() { keyboard.type(); } } ✅ Good Example interface Keyboard { void type(); } class WiredKeyboard implements Keyboard { public void type() { System.out.println("Typing with Wired Keyboard"); } } class WirelessKeyboard implements Keyboard { public void type() { System.out.println("Typing with Wireless Keyboard"); } } class Computer { private Keyboard keyboard; Computer(Keyboard keyboard) { this.keyboard = keyboard; } void work() { keyboard.type(); } } public class Main { public static void main(String[] args) { Keyboard keyboard = new WirelessKeyboard(); Computer computer = new Computer(keyboard); computer.work(); } }
SRP: Employee → Employee + SalaryCalc + EmployeeRepo OCP: Shape → Add Triangle without modifying AreaCalc LSP: Square should NOT extend Rectangle ISP: Worker → Workable + Eatable + Sleepable DIP: NotificationService + MessageSender interface
4. Design Patterns
4.1 Singleton Pattern
Theory: Ensures a class has only one instance and provides global access.
Types of Singleton Implementation
1. Eager Initialization
public class EagerSingleton { // Create instance at class loading private static final EagerSingleton INSTANCE = new EagerSingleton(); private EagerSingleton() { } public static EagerSingleton getInstance() { return INSTANCE; } }
2. Lazy Initialization
public class LazySingleton { private static LazySingleton instance; private LazySingleton() { } public static LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; } }
3. Thread-safe Singleton
public class ThreadSafeSingleton { private static volatile ThreadSafeSingleton instance; private ThreadSafeSingleton() { } public static ThreadSafeSingleton getInstance() { if (instance == null) { synchronized (ThreadSafeSingleton.class) { if (instance == null) { instance = new ThreadSafeSingleton(); } } } return instance; } }
4. Bill Pugh Singleton (Best Practice)
public class BillPughSingleton { private BillPughSingleton() { } private static class SingletonHelper { private static final BillPughSingleton INSTANCE = new BillPughSingleton(); } public static BillPughSingleton getInstance() { return SingletonHelper.INSTANCE; } }
5. Enum Singleton (Most Secure)
public enum EnumSingleton { INSTANCE; public void doSomething() { System.out.println("Doing something..."); } } // Usage EnumSingleton.INSTANCE.doSomething();
4.2 Factory Pattern
Theory: Creates objects without exposing instantiation logic to client.
// Product Interface interface Animal { void speak(); } // Concrete Products class Dog implements Animal { @Override public void speak() { System.out.println("Woof!"); } } class Cat implements Animal { @Override public void speak() { System.out.println("Meow!"); } } // Factory Class class AnimalFactory { public static Animal createAnimal(String type) { if (type == null || type.isEmpty()) { return null; } switch(type.toLowerCase()) { case "dog": return new Dog(); case "cat": return new Cat(); default: throw new IllegalArgumentException("Unknown animal: " + type); } } } // Usage public class FactoryDemo { public static void main(String[] args) { Animal dog = AnimalFactory.createAnimal("dog"); Animal cat = AnimalFactory.createAnimal("cat"); dog.speak(); // Woof! cat.speak(); // Meow! } }
5. Exception Handling
5.1 Exception Hierarchy
Throwable ├── Error (Unchecked) - JVM errors, OutOfMemoryError, StackOverflowError └── Exception ├── RuntimeException (Unchecked) - Programming errors │ ├── NullPointerException │ ├── ArrayIndexOutOfBoundsException │ ├── ArithmeticException │ └── ClassCastException └── Checked Exceptions - Must handle or declare ├── IOException ├── SQLException └── InterruptedException
5.2 Exception Handling Keywords
try-catch-finally
public class ExceptionDemo { public static void main(String[] args) { try { int result = 10 / 0; // ArithmeticException int[] arr = new int[5]; arr[10] = 100; // ArrayIndexOutOfBoundsException } catch (ArithmeticException e) { System.out.println("Cannot divide by zero!"); System.out.println("Message: " + e.getMessage()); e.printStackTrace(); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Array index out of bounds!"); } catch (Exception e) { System.out.println("Generic exception handler"); } finally { System.out.println("This always executes"); } } }
throw and throws
// Checked Exception public class ThrowDemo { // Method declares it throws exception public static void validateAge(int age) throws IllegalArgumentException { if (age < 0 || age > 120) { // Throwing exception manually throw new IllegalArgumentException("Invalid age: " + age); } System.out.println("Valid age: " + age); } public static void main(String[] args) { try { validateAge(150); } catch (IllegalArgumentException e) { System.out.println("Caught: " + e.getMessage()); } } }
Custom Exception
// Custom checked exception class InsufficientFundsException extends Exception { private double amount; public InsufficientFundsException(String message, double amount) { super(message); this.amount = amount; } public double getAmount() { return amount; } } // Custom unchecked exception class InvalidTransactionException extends RuntimeException { public InvalidTransactionException(String message) { super(message); } } // Usage class BankAccount { private double balance = 1000; public void withdraw(double amount) throws InsufficientFundsException { if (amount > balance) { throw new InsufficientFundsException( "Insufficient balance", amount - balance ); } balance -= amount; } }
5.3 Best Practices
- Always use
finallyto release resources - Use specific exceptions, not generic
Exception - Don't ignore exceptions (empty catch block)
- Log exceptions appropriately
- Use
try-with-resources(Java 7+)
// try-with-resources (AutoCloseable) try (FileReader fr = new FileReader("file.txt"); BufferedReader br = new BufferedReader(fr)) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } } catch (IOException e) { System.err.println("Error reading file: " + e.getMessage()); }
6. Collections Framework
6.1 Collection Hierarchy
Iterable └── Collection ├── List (Ordered, allows duplicates) │ ├── ArrayList (Dynamic array) │ ├── LinkedList (Doubly linked list) │ └── Vector (Thread-safe) │ └── Stack ├── Set (No duplicates, unordered) │ ├── HashSet (Hash table) │ │ └── LinkedHashSet (Preserves insertion order) │ └── TreeSet (Sorted, Red-Black tree) └── Queue (FIFO) └── PriorityQueue (Heap) └── Deque Map (Not part of Collection) ├── HashMap (Hash table) │ └── LinkedHashMap (Preserves insertion order) ├── TreeMap (Sorted, Red-Black tree) └── Hashtable (Thread-safe)
6.2 List Interface
ArrayList
import java.util.*; public class ArrayListDemo { public static void main(String[] args) { // Creation ArrayList<String> list = new ArrayList<>(); // Adding elements list.add("Apple"); list.add("Banana"); list.add("Orange"); list.add(1, "Mango"); // Add at index // Accessing String fruit = list.get(2); // Get by index list.set(1, "Grapes"); // Update // Removing list.remove("Apple"); // By value list.remove(1); // By index // Size int size = list.size(); // Checking boolean contains = list.contains("Banana"); int index = list.indexOf("Orange"); // Iteration // 1. For-each for (String s : list) { System.out.println(s); } // 2. Iterator Iterator<String> it = list.iterator(); while (it.hasNext()) { System.out.println(it.next()); } // 3. Java 8 Stream list.forEach(System.out::println); } }
LinkedList
public class LinkedListDemo { public static void main(String[] args) { LinkedList<String> list = new LinkedList<>(); // List methods list.add("First"); list.add("Second"); // LinkedList-specific methods list.addFirst("Start"); list.addLast("End"); list.removeFirst(); list.removeLast(); String first = list.getFirst(); String last = list.getLast(); // Use as Queue list.offer("Element"); // Add String element = list.poll(); // Remove and return String peek = list.peek(); // View without removing } }
6.3 Set Interface
HashSet
public class HashSetDemo { public static void main(String[] args) { HashSet<String> set = new HashSet<>(); set.add("Apple"); set.add("Banana"); set.add("Apple"); // Won't be added (duplicate) System.out.println("Size: " + set.size()); // 2 // Checks boolean hasApple = set.contains("Apple"); set.remove("Banana"); boolean isEmpty = set.isEmpty(); // Iteration (unordered) for (String s : set) { System.out.println(s); } } }
6.4 Map Interface
HashMap
public class HashMapDemo { public static void main(String[] args) { HashMap<String, Integer> map = new HashMap<>(); // Adding key-value pairs map.put("John", 25); map.put("Alice", 30); map.put("Bob", 35); map.put("John", 26); // Updates value // Accessing int johnAge = map.get("John"); int defaultValue = map.getOrDefault("Mary", 0); // Checking boolean hasKey = map.containsKey("Alice"); boolean hasValue = map.containsValue(30); // Removing map.remove("Bob"); // Iteration // 1. EntrySet for (Map.Entry<String, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + " = " + entry.getValue()); } // 2. KeySet for (String key : map.keySet()) { System.out.println(key + " -> " + map.get(key)); } // 3. Java 8 map.forEach((key, value) -> System.out.println(key + ": " + value)); } }
6.5 Sorting and Comparators
Comparable
// Natural ordering class Student implements Comparable<Student> { String name; int rollNo; Student(String name, int rollNo) { this.name = name; this.rollNo = rollNo; } @Override public int compareTo(Student other) { return this.rollNo - other.rollNo; // Ascending // return other.rollNo - this.rollNo; // Descending // return this.name.compareTo(other.name); // String } @Override public String toString() { return name + "(" + rollNo + ")"; } } // Usage List<Student> students = new ArrayList<>(); students.add(new Student("Alice", 103)); students.add(new Student("Bob", 101)); students.add(new Student("Charlie", 102)); Collections.sort(students); // Sorts by rollNo
Comparator
// Custom ordering class NameComparator implements Comparator<Student> { @Override public int compare(Student s1, Student s2) { return s1.name.compareTo(s2.name); } } // Usage Collections.sort(students, new NameComparator()); // Java 8 Lambda Collections.sort(students, (s1, s2) -> s1.name.compareTo(s2.name)); // Reverse order Collections.sort(students, Comparator.reverseOrder());
6.6 Important Collection Methods
// Collections utility class Collections.sort(list); // Sort Collections.reverse(list); // Reverse Collections.shuffle(list); // Randomize Collections.copy(dest, src); // Copy Collections.frequency(list, obj); // Count occurrences Collections.max(list); // Max value Collections.min(list); // Min value Collections.binarySearch(list, key); // Binary search // Converting // Array to List String[] arr = {"a", "b", "c"}; List<String> list = Arrays.asList(arr); // List to Array String[] array = list.toArray(new String[0]); // Synchronized versions List<String> syncList = Collections.synchronizedList(new ArrayList<>()); Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
7. Multithreading
Multithreading is the process of executing multiple threads concurrently within a single program to perform multiple tasks efficiently.
A thread is the smallest unit of execution inside a process.
Example
Without multithreading:
Task 1 → Task 2 → Task 3
With multithreading:
Thread 1 → Task 1 Thread 2 → Task 2 Thread 3 → Task 3
All tasks run concurrently.
-
✅ Better CPU utilization
-
✅ Faster execution
-
✅ Perform multiple tasks simultaneously
-
✅ Improves responsiveness (e.g., GUI applications)
-
Runnable→ Preferred because Java supports single inheritance, and implementing an interface lets your class extend another class if needed. -
Thread→ Simpler for basic examples but less flexible.
A race condition occurs when multiple threads access and modify shared data simultaneously, leading to unpredictable results.
New ↓ Runnable ↓ Running ↓ Blocked / Waiting (optional) ↓ Terminated
The thread object is created, but
start()has not been called yet. After callingstart(), the thread is ready to run and waits for CPU time. The CPU picks the thread, and it starts executing therun()method. The thread is temporarily paused because it is waiting for a lock, another thread, or a timeout (e.g.,sleep(),wait(),join()). The thread finishes executing itsrun()method or stops due to an exception.
7.1 Thread Creation
1. Extending Thread Class
class MyThread extends Thread { @Override public void run() { for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + ": " + i); try { Thread.sleep(1000); // Pause for 1 second } catch (InterruptedException e) { System.out.println("Thread interrupted"); } } } } // Usage MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.start(); // Start thread t2.start();
2. Implementing Runnable Interface
class MyRunnable implements Runnable { @Override public void run() { for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + ": " + i); try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } // Usage Thread t1 = new Thread(new MyRunnable(), "Thread-1"); Thread t2 = new Thread(new MyRunnable(), "Thread-2"); t1.start(); t2.start();
start() vs run()
t.start(); // Creates a new thread ✅ t.run(); // Normal method call ❌
Always use start() to create a new thread.
3. Java 8 Lambda
Runnable task = () -> { for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + ": " + i); } }; new Thread(task).start();
7.2 Thread Lifecycle
NEW → READY → RUNNING → TERMINATED ↓ ↓ WAITING BLOCKED TIMED_WAITING
7.3 Thread Methods
// Important thread methods t.start(); // Start thread t.run(); // Called by JVM t.sleep(1000); // Static - Pause current thread t.join(); // Wait for thread to finish t.join(2000); // Wait max 2 seconds t.yield(); // Yield CPU time t.setPriority(10); // Set priority (1-10) t.setDaemon(true); // Daemon thread t.interrupt(); // Interrupt thread
7.4 Synchronization
Problem without Synchronization
class Counter { int count = 0; void increment() { count++; // Not atomic } } // Race condition problem Counter counter = new Counter(); Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) counter.increment(); }); Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) counter.increment(); }); // count may not be 2000
Solution 1: Synchronized Method
class SynchronizedCounter { int count = 0; synchronized void increment() { count++; // Thread-safe } synchronized int getCount() { return count; } }
Solution 2: Synchronized Block
class SharedResource { int count = 0; void increment() { synchronized (this) { count++; } } // Better: Use separate lock object private final Object lock = new Object(); void safeIncrement() { synchronized (lock) { count++; } } }
7.5 Inter-thread Communication
Producer-Consumer Example
class SharedBuffer { private int data; private boolean available = false; synchronized void produce(int value) throws InterruptedException { while (available) { wait(); // Wait if buffer has data } data = value; System.out.println("Produced: " + value); available = true; notify(); // Notify consumer } synchronized int consume() throws InterruptedException { while (!available) { wait(); // Wait if no data } available = false; System.out.println("Consumed: " + data); notify(); // Notify producer return data; } } // Usage SharedBuffer buffer = new SharedBuffer(); Thread producer = new Thread(() -> { for (int i = 1; i <= 5; i++) { try { buffer.produce(i); Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); Thread consumer = new Thread(() -> { for (int i = 1; i <= 5; i++) { try { int val = buffer.consume(); Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } });
7.6 Executor Framework
import java.util.concurrent.*; public class ExecutorDemo { public static void main(String[] args) { // 1. Fixed thread pool ExecutorService executor = Executors.newFixedThreadPool(3); // Submit tasks for (int i = 0; i < 10; i++) { executor.execute(() -> { System.out.println(Thread.currentThread().getName() + " is running"); }); } // 2. Cached thread pool ExecutorService cachedPool = Executors.newCachedThreadPool(); // 3. Single thread executor ExecutorService single = Executors.newSingleThreadExecutor(); // Shutdown executor.shutdown(); // With return value Future<String> future = executor.submit(() -> { Thread.sleep(1000); return "Task completed"; }); try { String result = future.get(); // Blocking System.out.println(result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } }
7.7 Concurrent Collections
// Thread-safe collections // 1. ConcurrentHashMap - Thread-safe HashMap ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); // 2. CopyOnWriteArrayList - Thread-safe ArrayList CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); // 3. BlockingQueue BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10); // 4. CountDownLatch CountDownLatch latch = new CountDownLatch(3); // 5. CyclicBarrier CyclicBarrier barrier = new CyclicBarrier(3); // 6. Semaphore Semaphore semaphore = new Semaphore(2); // 7. Atomic classes AtomicInteger atomicInt = new AtomicInteger(0); atomicInt.incrementAndGet(); atomicInt.decrementAndGet();
8. Interview Questions & Important Points
8.1 Quick Reference Questions
Java Basics
-
What is JDK, JRE, JVM?
- JDK = Development tools + JRE
- JRE = Libraries + JVM
- JVM = Executes bytecode
-
Difference between == and equals()?
==compares references (memory address)equals()compares content (overridden in String)
-
Difference between String, StringBuilder, StringBuffer?
- String: Immutable, thread-safe
- StringBuilder: Mutable, not thread-safe (fastest)
- StringBuffer: Mutable, thread-safe
-
What are access modifiers?
private: Only within classdefault: Within packageprotected: Within package + subclassespublic: Everywhere
-
Difference between abstract class and interface?
- Abstract: 0-100%, can have instance variables, constructor
- Interface: 100% abstract (before Java 8), static final variables
- Abstract Class: Use when classes share common state and implementation.
- Interface: Use when you want to define a common contract that different classes can implement. An abstract class provides partial implementation and can maintain state, while an interface defines a contract for behavior and supports multiple inheritance through implementation.
- What is method overloading vs overriding?
- Overloading: Same name, different params (compile-time)
- Overriding: Same signature in child class (runtime)
Advanced Topics
-
What is garbage collection?
- Automatic memory management
System.gc()suggests but doesn't force- Garbage Collection (GC) is the automatic process of removing unused objects from memory (heap) to free up space.
-
Difference between checked and unchecked exceptions?
- Checked: Compile-time (IOException, SQLException)
- Unchecked: Runtime (NullPointerException, ArithmeticException)
-
What is fail-fast and fail-safe?
- Fail-fast: Immediately throw exception on modification (ArrayList)
- Fail-safe: Work on copy (ConcurrentHashMap, CopyOnWriteArrayList)
-
Difference between HashMap and HashTable?
- HashMap: Not synchronized, allows null
- HashTable: Synchronized, doesn't allow null
8.2 Code Snippets for Quick Revision
String Manipulation
// String Immutability String s1 = "Hello"; String s2 = s1.concat(" World"); // New object created String s3 = "Hello World"; // String equality String str1 = new String("Hello"); String str2 = new String("Hello"); str1 == str2; // false str1.equals(str2); // true // StringBuilder StringBuilder sb = new StringBuilder(); sb.append("Hello").append(" World"); String result = sb.toString();
Common Utility Methods
// String methods "Hello".length() // 5 "Hello".charAt(1) // 'e' "Hello".substring(1, 3) // "el" "Hello".toLowerCase() // "hello" "Hello".toUpperCase() // "HELLO" "Hello".indexOf('e') // 1 "Hello".contains("el") // true "Hello".replace('l', 'p') // "Heppo" " Hello ".trim() // "Hello" String.join("-", "a", "b", "c") // "a-b-c" // Array methods Arrays.sort(arr) Arrays.binarySearch(arr, key) Arrays.fill(arr, value) Arrays.copyOf(arr, newLength) Arrays.equals(arr1, arr2) // Collections methods Collections.sort(list) Collections.reverse(list) Collections.shuffle(list) Collections.binarySearch(list, key) Collections.max(list) Collections.min(list) Collections.frequency(list, obj)
8.3 Important Interview Topics
OOPs Concepts to Master
- Encapsulation - Getter/Setter, Data hiding
- Inheritance - Types,
super,this - Polymorphism - Overloading vs Overriding
- Abstraction - Abstract classes, Interfaces
- Association, Aggregation, Composition
- Cohesion and Coupling
Multi-threading Concepts
- Thread lifecycle states
synchronizedkeyword and its usewait(),notify(),notifyAll()- Deadlock and prevention
- Thread pooling (ExecutorService)
- Concurrent Collections
Collections Framework
- Hierarchy and interfaces
- Differences between List, Set, Map
- Implementation details (ArrayList vs LinkedList, etc.)
- Comparable vs Comparator
- Internal working of HashMap
- Custom sorting
8.4 Java 8 Features (Important)
// Lambda Expressions list.forEach(item -> System.out.println(item)); // Functional Interfaces @FunctionalInterface interface MyInterface { void execute(); } // Method References list.forEach(System.out::println); list.forEach(String::toUpperCase); // Stream API list.stream() .filter(s -> s.startsWith("A")) .map(String::toUpperCase) .sorted() .collect(Collectors.toList()); // Optional Optional<String> optional = Optional.ofNullable(getValue()); optional.ifPresent(System.out::println); // Default methods in Interface interface DefaultInterface { default void defaultMethod() { System.out.println("Default method"); } } // Static methods in Interface interface StaticInterface { static void staticMethod() { System.out.println("Static method"); } }
8.5 Common Mistakes to Avoid
- ❌ Forgetting to override
equals()andhashCode() - ❌ Using == for string comparison
- ❌ Not handling exceptions properly
- ❌ Modifying collection while iterating
- ❌ Not using
finallyfor resource cleanup - ❌ Creating unnecessary objects
- ❌ Not understanding immutability
- ❌ Ignoring thread-safety issues