📚 Chapters

☕ Java Programming

Unit 2 — Packages, Exceptions & Multithreading

📦 Chapter 1 — Packages & IOStream
📦 PACKAGES & IO - MIND MAP
Built-in Packages → java.lang (auto), java.util (collections), java.io (files), java.awt (GUI), java.math (big numbers), java.sql (database)
User Packages → package keyword, directory structure, import statement, CLASSPATH
IO Streams → Byte streams (binary data), Character streams (text data)
File Operations → Read (FileReader, BufferedReader), Write (FileWriter, PrintWriter)

1. Built-in Packages (java.awt, java.io, java.lang, java.math, java.sql, java.util)

📖 What is a Package?
A package is a namespace that organizes related classes and interfaces. It prevents naming conflicts, provides access control, and makes code easier to maintain.
java.lang - Language Package (Automatically Imported):
📖 java.lang:
Automatically imported into every Java program. Contains core classes: String, Math, System, Integer, Thread, Object, Exception.

Key Point: No need to write import java.lang.* - it's automatic!
// java.lang is auto-imported - no import needed! // String class String name = "Ankush"; int length = name.length(); // Math class double result = Math.sqrt(25); // 5.0 int maximum = Math.max(10, 20); // 20 // System class System.out.println("Hello World"); // Wrapper classes Integer num = Integer.parseInt("123"); Double decimal = Double.parseDouble("45.67"); // Object class (parent of all classes) Object obj = new String("test"); Output: Hello World
java.util - Utility Package:
📖 java.util:
Contains utility classes for collections, date/time, random numbers. Must be imported before use.
Common classes: ArrayList, HashMap, Scanner, Date, Random.
import java.util.*; // ArrayList - dynamic array ArrayList fruits = new ArrayList<>(); fruits.add("Apple"); fruits.add("Banana"); fruits.add("Orange"); System.out.println(fruits); // [Apple, Banana, Orange] // HashMap - key-value pairs HashMap marks = new HashMap<>(); marks.put("Raj", 85); marks.put("Priya", 92); System.out.println(marks.get("Raj")); // 85 // Scanner - user input Scanner sc = new Scanner(System.in); System.out.print("Enter name: "); String name = sc.nextLine(); // Date Date today = new Date(); System.out.println(today); // Random Random rand = new Random(); int randomNum = rand.nextInt(100); // 0 to 99
java.io - Input/Output Package:
📖 java.io:
Provides classes for input/output through data streams and file handling.
Common classes: File, FileReader, FileWriter, BufferedReader, PrintWriter.
import java.io.*; // Reading from file try { FileReader fr = new FileReader("data.txt"); BufferedReader br = new BufferedReader(fr); String line; while ((line = br.readLine()) != null) { System.out.println(line); } br.close(); } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } // Writing to file try { FileWriter fw = new FileWriter("output.txt"); PrintWriter pw = new PrintWriter(fw); pw.println("Hello File!"); pw.println("Java is awesome"); pw.close(); System.out.println("File written successfully"); } catch (IOException e) { System.out.println("Error: " + e.getMessage()); }
java.awt - Abstract Window Toolkit:
📖 java.awt:
Contains classes for creating GUI and handling graphics.
Common classes: Frame, Button, Label, TextField, Panel, Color, Font.
import java.awt.*; // Creating a simple window Frame f = new Frame("My First Window"); Button b = new Button("Click Me"); Label l = new Label("Welcome!"); f.add(b); f.add(l); f.setSize(400, 300); f.setLayout(new FlowLayout()); f.setVisible(true); // Output: A window appears with a button and label
java.math - Mathematics Package:
📖 java.math:
Provides classes for arbitrary-precision arithmetic beyond primitive type ranges.
Common classes: BigInteger (large integers), BigDecimal (precise decimals).
💡 Why BigInteger/BigDecimal?
long max: 9,223,372,036,854,775,807
BigInteger max: Unlimited! Can store numbers with millions of digits
BigDecimal: Precise money calculations (no rounding errors)
import java.math.*; // BigInteger - very large numbers BigInteger big1 = new BigInteger("123456789012345678901234567890"); BigInteger big2 = new BigInteger("987654321098765432109876543210"); BigInteger sum = big1.add(big2); BigInteger product = big1.multiply(big2); System.out.println("Sum: " + sum); System.out.println("Product: " + product); // BigDecimal - precise decimal math BigDecimal price1 = new BigDecimal("19.99"); BigDecimal price2 = new BigDecimal("5.01"); BigDecimal total = price1.add(price2); System.out.println("Total: $" + total); // $25.00 (exact!)
java.sql - SQL Package:
📖 java.sql:
Provides classes and interfaces for database access via JDBC.
Common interfaces: Connection, Statement, ResultSet, DriverManager.
import java.sql.*; // Database connection and query try { // 1. Load driver (automatic in modern Java) // 2. Establish connection Connection con = DriverManager.getConnection( "jdbc:mysql://localhost:3306/college", "root", "password" ); // 3. Create statement Statement stmt = con.createStatement(); // 4. Execute query ResultSet rs = stmt.executeQuery("SELECT * FROM students"); // 5. Process results while (rs.next()) { int id = rs.getInt("id"); String name = rs.getString("name"); int marks = rs.getInt("marks"); System.out.println(id + " | " + name + " | " + marks); } // 6. Close connection con.close(); } catch (SQLException e) { System.out.println("Database Error: " + e.getMessage()); } Output: 1 | Raj | 85 2 | Priya | 92 3 | Amit | 78
💡 Package Summary:
java.lang - Core (String, Math, System) - AUTO IMPORTED
java.util - Utilities (ArrayList, HashMap, Scanner)
java.io - File I/O (FileReader, FileWriter)
java.awt - GUI (Frame, Button, Label)
java.math - Big Numbers (BigInteger, BigDecimal)
java.sql - Database (Connection, Statement, ResultSet)

2. Creating User Defined Packages

📖 Definition:
User-defined packages are custom packages created by programmers. The 'package' keyword must be the first statement in a Java source file.

Why? Code organization, avoid naming conflicts, access control.
💡 Analogy: Like company departments: com.company.hr (HR classes), com.company.finance (Finance classes), com.company.sales (Sales classes).
Steps to Create a Package:
1. Declare package: package packagename; (first line)
2. Write your class code
3. Save file: ClassName.java
4. Compile: javac -d . FileName.java
5. Directory structure automatically created
// File: Student.java // Step 1: Package declaration (MUST be first) package college.students; // Step 2: Imports (if needed) import java.util.*; // Step 3: Class definition public class Student { private String name; private int rollNo; public Student(String name, int rollNo) { this.name = name; this.rollNo = rollNo; } public void display() { System.out.println("Name: " + name); System.out.println("Roll No: " + rollNo); } } // Compile command: // javac -d . Student.java // Directory structure created: // college/ // students/ // Student.class
Sub-packages (Nested Packages):
📖 Sub-packages:
Packages within packages, creating a hierarchy. Use dots (.) to separate levels.
Example: college.students.engineering (3 levels)
// File: CSEStudent.java package college.students.engineering.cse; public class CSEStudent { String branch = "Computer Science"; String semester = "5th"; public void showDetails() { System.out.println("Branch: " + branch); System.out.println("Semester: " + semester); } } // Directory structure: // college/ // students/ // engineering/ // cse/ // CSEStudent.class
💡 Package Naming Conventions:
• Use lowercase letters only
• Reverse domain name: com.company.project
• Avoid Java keywords (int, class, public, etc.)
• Use dots to separate levels
• Example: com.ankushraj.notes.java
// Good package names: package com.company.project.module; package edu.university.department; package org.opensource.library; // Bad package names: package MyPackage; // Capital letters package com.company.class; // 'class' is keyword package 123project; // Starts with number

3. Accessing a Package

📖 Definition:
To use classes from another package, you must make them accessible via import statement (recommended) or fully qualified name.
Method 1: Using import Statement:
Import specific class: import packagename.ClassName;
Import all classes: import packagename.*;
// Import specific class import java.util.ArrayList; import college.students.Student; public class Main { public static void main(String[] args) { ArrayList list = new ArrayList<>(); // OK Student s = new Student("Raj", 101); // OK s.display(); } } // Import all classes from package import java.util.*; public class Test { public static void main(String[] args) { ArrayList list = new ArrayList<>(); // OK HashMap map = new HashMap<>(); // OK Scanner sc = new Scanner(System.in); // OK } }
Method 2: Fully Qualified Name:
📖 Fully Qualified Name:
Using the complete package path before the class name, without import.
When to use: When two classes have the same name from different packages.
// No import needed - use full package path public class Main { public static void main(String[] args) { // Full package path java.util.ArrayList list = new java.util.ArrayList<>(); college.students.Student s = new college.students.Student("Raj", 101); } } // Solving name conflicts public class DateExample { public static void main(String[] args) { // Both packages have Date class! java.util.Date utilDate = new java.util.Date(); // Current date java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis()); // SQL date System.out.println("Util Date: " + utilDate); System.out.println("SQL Date: " + sqlDate); } }
Static Import:
📖 Static Import:
Imports static members directly so they can be used without the class name prefix.
Syntax: import static packagename.ClassName.memberName;
// Without static import public class Test1 { public static void main(String[] args) { System.out.println(Math.PI); // Need Math. System.out.println(Math.max(10, 20)); // Need Math. System.out.println(Math.sqrt(25)); // Need Math. } } // With static import import static java.lang.Math.*; // Import all static members public class Test2 { public static void main(String[] args) { System.out.println(PI); // Direct use! System.out.println(max(10, 20)); // Direct use! System.out.println(sqrt(25)); // Direct use! } } Output (both programs): 3.141592653589793 20 5.0
💡 Import Best Practices:
• Use specific imports when possible: import java.util.ArrayList;
• Use import package.*; for multiple classes from same package
• Use fully qualified names only for name conflicts
• Static import: use sparingly (can make code confusing)

4. Using a Package

📖 Definition:
Once imported, package classes are used like built-in classes. CLASSPATH tells Java where to find packages.
CLASSPATH: List of directories/JAR files where Java searches for classes.
💡 Complete Example: Creating and Using Custom Package
// Step 1: Create package class // File: Calculator.java package mypackage.utils; public class Calculator { public int add(int a, int b) { return a + b; } public int subtract(int a, int b) { return a - b; } public int multiply(int a, int b) { return a * b; } } // Compile: // javac -d . Calculator.java // Creates: mypackage/utils/Calculator.class
// Step 2: Use package in another program // File: Main.java import mypackage.utils.Calculator; public class Main { public static void main(String[] args) { Calculator calc = new Calculator(); System.out.println("10 + 5 = " + calc.add(10, 5)); System.out.println("10 - 5 = " + calc.subtract(10, 5)); System.out.println("10 * 5 = " + calc.multiply(10, 5)); } } // Compile: // javac Main.java // Run: // java Main Output: 10 + 5 = 15 10 - 5 = 5 10 * 5 = 50
Setting CLASSPATH:
// Windows: set CLASSPATH=.;C:\myproject\classes;C:\libraries\lib.jar // Linux/Mac: export CLASSPATH=.:/home/user/myproject/classes:/libs/lib.jar // In Java command: java -cp .:/path/to/classes Main // Dot (.) means current directory

5. Input/Output Streams

📖 What is a Stream?
A sequence of data flowing from source to destination for I/O operations.

Two types: Input Stream (reading data) and Output Stream (writing data).
Stream Classification:
1. Byte Streams: Handle binary data (images, videos, any file). Read/write 8 bits (1 byte) at a time.
• Input: InputStream (abstract parent class)
• Output: OutputStream (abstract parent class)

2. Character Streams: Handle text data (only text files). Read/write 16 bits (Unicode characters) at a time.
• Input: Reader (abstract parent class)
• Output: Writer (abstract parent class)
Feature Byte Stream Character Stream
Data Type Binary (8-bit bytes) Text (16-bit Unicode)
Parent Classes InputStream, OutputStream Reader, Writer
Use For Images, videos, any file Text files only
Examples FileInputStream, FileOutputStream FileReader, FileWriter
Byte Stream Classes:
import java.io.*; // FileInputStream - Read bytes from file public class ByteStreamExample { public static void main(String[] args) { try { FileInputStream fis = new FileInputStream("image.jpg"); int data; // Read byte by byte while ((data = fis.read()) != -1) { // Process byte data System.out.print(data + " "); } fis.close(); System.out.println("\nFile read successfully!"); } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } } } // FileOutputStream - Write bytes to file public class ByteWriteExample { public static void main(String[] args) { try { FileOutputStream fos = new FileOutputStream("output.dat"); String data = "Hello Bytes!"; // Convert string to bytes and write fos.write(data.getBytes()); fos.close(); System.out.println("Data written successfully!"); } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } } }
Character Stream Classes:
import java.io.*; // FileReader - Read characters from file public class CharStreamExample { public static void main(String[] args) { try { FileReader fr = new FileReader("data.txt"); int ch; // Read character by character while ((ch = fr.read()) != -1) { System.out.print((char) ch); } fr.close(); } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } } } // FileWriter - Write characters to file public class CharWriteExample { public static void main(String[] args) { try { FileWriter fw = new FileWriter("output.txt"); fw.write("Hello World!\n"); fw.write("Java File I/O is easy!"); fw.close(); System.out.println("File written successfully!"); } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } } }
💡 When to use which stream:
Byte Streams: Images (.jpg, .png), Videos (.mp4), Audio (.mp3), Binary files, Any non-text file
Character Streams: Text files (.txt), Java source (.java), HTML files (.html), CSV files

6. IO Files for Reading and Writing Data from File

📖 File I/O Operations:
Java provides classes to read from and write to files.

Key classes: FileReader, BufferedReader (reading), FileWriter, PrintWriter (writing).
Reading from File - Line by Line:
BufferedReader: Reads text efficiently by buffering characters. Provides readLine() for reading entire lines. Faster than FileReader alone.
import java.io.*; public class FileReadExample { public static void main(String[] args) { try { // Create FileReader FileReader fr = new FileReader("students.txt"); // Wrap in BufferedReader for efficiency BufferedReader br = new BufferedReader(fr); String line; int lineNumber = 1; // Read line by line while ((line = br.readLine()) != null) { System.out.println(lineNumber + ": " + line); lineNumber++; } // Close the reader br.close(); } catch (FileNotFoundException e) { System.out.println("File not found: " + e.getMessage()); } catch (IOException e) { System.out.println("Error reading file: " + e.getMessage()); } } } // If students.txt contains: // Raj // Priya // Amit Output: 1: Raj 2: Priya 3: Amit
Writing to File - PrintWriter:
PrintWriter: Writes formatted text to files. Provides println(), print(), printf() methods like System.out.
import java.io.*; public class FileWriteExample { public static void main(String[] args) { try { // Create FileWriter FileWriter fw = new FileWriter("output.txt"); // Wrap in PrintWriter for convenience PrintWriter pw = new PrintWriter(fw); // Write data pw.println("Student Records"); pw.println("================"); pw.println("Name: Raj Kumar"); pw.println("Roll No: 101"); pw.println("Marks: 85"); pw.println(); pw.println("Name: Priya Sharma"); pw.println("Roll No: 102"); pw.println("Marks: 92"); // Close the writer pw.close(); System.out.println("Data written to output.txt successfully!"); } catch (IOException e) { System.out.println("Error writing file: " + e.getMessage()); } } } Output (in output.txt file): Student Records ================ Name: Raj Kumar Roll No: 101 Marks: 85 Name: Priya Sharma Roll No: 102 Marks: 92
💡 File I/O Best Practices:
• Always close streams after use (or use try-with-resources)
• Handle FileNotFoundException separately from IOException
• Use BufferedReader for efficient line-by-line reading
• Use PrintWriter for formatted writing
• Append mode: new FileWriter("file.txt", true)
• Check if file exists: new File("name.txt").exists();
💡 Try-with-resources (Automatic Close):
import java.io.*; public class AutoCloseExample { public static void main(String[] args) { // Try-with-resources automatically closes streams try (BufferedReader br = new BufferedReader( new FileReader("data.txt"))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } // No need to call br.close() - automatic! } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } } }
⚠️ Chapter 2 — Exception Handling
⚠️ EXCEPTION HANDLING - MIND MAP
What is Exception? → Runtime error that disrupts normal program flow
Built-in Exceptions → NullPointerException, ArithmeticException, ArrayIndexOutOfBounds, etc.
User-defined → Create custom exceptions extending Exception class
Handling → try-catch blocks, multiple catch for different exceptions
finally → Always executes (cleanup code)
throws → Declare exceptions in method signature

1. Java's Built-in Exceptions

📖 What is an Exception?
An abnormal event or runtime error that disrupts the normal flow of program execution. Without handling, the program terminates abruptly.

Why handle? Prevents crashes, maintains flow, provides user-friendly error messages, ensures resource cleanup.
💡 Analogy: Like a flat tire while driving — exception handling means stopping safely and fixing it instead of crashing.
Exception Hierarchy:
Object (parent of all) └── Throwable ├── Error (serious problems - NOT handled) │ ├── OutOfMemoryError │ └── StackOverflowError │ └── Exception (handled by programmers) ├── IOException (Checked) ├── SQLException (Checked) └── RuntimeException (Unchecked) ├── NullPointerException ├── ArithmeticException ├── ArrayIndexOutOfBoundsException └── NumberFormatException
Two types of Exceptions:
1. Checked Exceptions: Checked at compile-time. Must handle with try-catch or throws.
2. Unchecked Exceptions: Checked at runtime. Handling optional (but recommended).
Common Built-in Exceptions:
1. NullPointerException:
Occurs when trying to use a null object reference.
String name = null; System.out.println(name.length()); // NullPointerException! // Output: Exception in thread "main" java.lang.NullPointerException // Fix: if (name != null) { System.out.println(name.length()); } else { System.out.println("Name is null"); }
2. ArithmeticException:
Occurs during mathematical operations (like division by zero).
int a = 10, b = 0; int result = a / b; // ArithmeticException: / by zero // Fix: if (b != 0) { int result = a / b; System.out.println("Result: " + result); } else { System.out.println("Cannot divide by zero"); }
3. ArrayIndexOutOfBoundsException:
Occurs when accessing array with invalid index (negative or >= array length).
int[] arr = {10, 20, 30}; System.out.println(arr[5]); // ArrayIndexOutOfBoundsException! // Fix: int index = 5; if (index >= 0 && index < arr.length) { System.out.println(arr[index]); } else { System.out.println("Invalid index: " + index); }
4. NumberFormatException:
Occurs when converting invalid string to number.
String str = "abc"; int num = Integer.parseInt(str); // NumberFormatException! // Fix: try { int num = Integer.parseInt(str); System.out.println("Number: " + num); } catch (NumberFormatException e) { System.out.println("Invalid number format: " + str); }
5. FileNotFoundException (Checked Exception):
Occurs when trying to access a file that doesn't exist. Must handle.
import java.io.*; // Must handle - compiler forces you! try { FileReader fr = new FileReader("nonexistent.txt"); } catch (FileNotFoundException e) { System.out.println("File not found: " + e.getMessage()); }
Exception Cause Type
NullPointerException Using null object Unchecked
ArithmeticException Division by zero Unchecked
ArrayIndexOutOfBoundsException Invalid array index Unchecked
NumberFormatException Invalid string to number Unchecked
FileNotFoundException File doesn't exist Checked
IOException I/O operation failed Checked
💡 Remember:
Checked: Compiler checks - MUST handle
Unchecked: Runtime checks - SHOULD handle
• All inherit from Exception class
• Use try-catch to handle exceptions

2. User Defined Exceptions

📖 What are User-defined Exceptions?
Custom exception classes created by extending Exception (checked) or RuntimeException (unchecked) for application-specific error handling.

Why? Better error categorization, domain-specific messages, easier debugging.
💡 Example: Banking app needs InsufficientFundsException, InvalidAccountException — built-in exceptions can't describe these specific errors.
Steps to Create User-defined Exception:
1. Create a class extending Exception (or RuntimeException)
2. Create constructors (default & parameterized)
3. Throw the exception using 'throw' keyword
4. Handle using try-catch
Example 1: Age Validation Exception:
// Step 1: Create custom exception class class InvalidAgeException extends Exception { // Default constructor public InvalidAgeException() { super("Invalid age provided"); } // Parameterized constructor public InvalidAgeException(String message) { super(message); } } // Step 2: Use in program class AgeValidator { public static void checkAge(int age) throws InvalidAgeException { if (age < 18) { throw new InvalidAgeException("Age must be 18 or above. You are: " + age); } System.out.println("Age verified: " + age); } } // Step 3: Handle exception public class Main { public static void main(String[] args) { try { AgeValidator.checkAge(15); // Will throw exception } catch (InvalidAgeException e) { System.out.println("Error: " + e.getMessage()); } } } Output: Error: Age must be 18 or above. You are: 15
Example 2: Bank Account Exception:
// Custom exception for insufficient balance class InsufficientFundsException extends Exception { private double amount; public InsufficientFundsException(double amount) { super("Insufficient funds. Need: " + amount); this.amount = amount; } public double getAmount() { return amount; } } // Bank account class class BankAccount { private double balance; public BankAccount(double balance) { this.balance = balance; } public void withdraw(double amount) throws InsufficientFundsException { if (amount > balance) { double shortage = amount - balance; throw new InsufficientFundsException(shortage); } balance -= amount; System.out.println("Withdrawal successful. New balance: " + balance); } public double getBalance() { return balance; } } // Using the custom exception public class BankDemo { public static void main(String[] args) { BankAccount account = new BankAccount(5000); try { System.out.println("Current balance: " + account.getBalance()); account.withdraw(3000); // OK account.withdraw(4000); // Will throw exception } catch (InsufficientFundsException e) { System.out.println("Transaction failed: " + e.getMessage()); System.out.println("Short by: " + e.getAmount()); } } } Output: Current balance: 5000.0 Withdrawal successful. New balance: 2000.0 Transaction failed: Insufficient funds. Need: 2000.0 Short by: 2000.0
Checked vs Unchecked Custom Exceptions:
// Checked exception (extends Exception) class InvalidEmailException extends Exception { public InvalidEmailException(String message) { super(message); } } // Unchecked exception (extends RuntimeException) class InvalidPasswordException extends RuntimeException { public InvalidPasswordException(String message) { super(message); } } // Usage difference class UserValidator { // Checked - must declare with 'throws' public static void validateEmail(String email) throws InvalidEmailException { if (!email.contains("@")) { throw new InvalidEmailException("Email must contain @"); } } // Unchecked - 'throws' optional public static void validatePassword(String password) { if (password.length() < 8) { throw new InvalidPasswordException("Password too short"); } } }
💡 Best Practices:
• Use meaningful exception names (suffixed with "Exception")
• Provide detailed error messages
• Extend Exception for checked, RuntimeException for unchecked
• Include relevant data in exception (like amount, age, etc.)
• Document when exceptions are thrown

3. Handling Multiple Exceptions

📖 Multiple Exception Handling:
Use multiple catch blocks to handle different exception types differently from a single try block.

Why? Different exceptions need different handling logic and error messages.
Method 1: Multiple Catch Blocks:
public class MultipleCatchExample { public static void main(String[] args) { try { int[] arr = {10, 20, 30}; // Can throw ArrayIndexOutOfBoundsException System.out.println(arr[5]); // Can throw ArithmeticException int result = 10 / 0; // Can throw NullPointerException String name = null; System.out.println(name.length()); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Error: Invalid array index"); System.out.println("Details: " + e.getMessage()); } catch (ArithmeticException e) { System.out.println("Error: Mathematical error"); System.out.println("Details: " + e.getMessage()); } catch (NullPointerException e) { System.out.println("Error: Null value encountered"); System.out.println("Details: " + e.getMessage()); } System.out.println("Program continues..."); } } Output: Error: Invalid array index Details: Index 5 out of bounds for length 3 Program continues...
Method 2: Multi-catch Block (Java 7+):
📖 Multi-catch:
Handle multiple exception types with same logic using single catch block. Exceptions separated by pipe (|) symbol.
public class MultiCatchExample { public static void main(String[] args) { try { String input = "abc"; int num = Integer.parseInt(input); // NumberFormatException int[] arr = {1, 2, 3}; System.out.println(arr[10]); // ArrayIndexOutOfBoundsException } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { // Same handling for both exceptions System.out.println("Error occurred: " + e.getClass().getSimpleName()); System.out.println("Message: " + e.getMessage()); } System.out.println("Program continues..."); } } Output: Error occurred: NumberFormatException Message: For input string: "abc" Program continues...
Catch Order Rules:
📖 Important Rule:
Catch blocks must be ordered from most specific to most general. Child exception must be caught before parent exception.
// CORRECT order - specific to general try { // code } catch (ArrayIndexOutOfBoundsException e) { // Specific System.out.println("Array error"); } catch (RuntimeException e) { // General (parent) System.out.println("Runtime error"); } catch (Exception e) { // Most general System.out.println("General error"); } // WRONG order - will not compile! try { // code } catch (Exception e) { // Most general first System.out.println("General error"); } catch (ArrayIndexOutOfBoundsException e) { // Unreachable! System.out.println("Array error"); // Compiler error! }
Complete Example - File Reading with Multiple Exceptions:
import java.io.*; public class FileReadMultipleCatch { public static void main(String[] args) { BufferedReader br = null; try { // Can throw FileNotFoundException br = new BufferedReader(new FileReader("data.txt")); // Can throw IOException String line = br.readLine(); System.out.println("First line: " + line); // Can throw NumberFormatException int number = Integer.parseInt(line); System.out.println("Number: " + number); // Can throw ArithmeticException int result = 100 / number; System.out.println("Result: " + result); } catch (FileNotFoundException e) { System.out.println("File not found: " + e.getMessage()); } catch (IOException e) { System.out.println("Error reading file: " + e.getMessage()); } catch (NumberFormatException e) { System.out.println("Invalid number format: " + e.getMessage()); } catch (ArithmeticException e) { System.out.println("Mathematical error: " + e.getMessage()); } catch (Exception e) { // Generic catch for any other exception System.out.println("Unexpected error: " + e.getMessage()); } finally { // Cleanup code (discussed in next section) try { if (br != null) { br.close(); } } catch (IOException e) { System.out.println("Error closing file"); } } } }
💡 Best Practices:
• Order catch blocks from specific to general
• Use multi-catch for same handling logic
• Always have a generic Exception catch at end (optional)
• Don't catch Exception too early - masks specific errors
• Log exception details for debugging

4. The finally Clause

📖 What is finally Block?
A block that always executes regardless of exception occurrence. Used for cleanup (closing files, connections).

Executes: After try (no exception), after catch (exception handled), even with return statement.
Syntax:
try { // Code that may throw exception } catch (ExceptionType e) { // Handle exception } finally { // Always executes (cleanup code) }
Example 1: finally Always Executes:
public class FinallyExample1 { public static void main(String[] args) { System.out.println("Start"); try { System.out.println("Inside try block"); int result = 10 / 2; // No exception System.out.println("Result: " + result); } catch (ArithmeticException e) { System.out.println("Inside catch block"); } finally { System.out.println("Inside finally block - ALWAYS executes"); } System.out.println("End"); } } Output: Start Inside try block Result: 5 Inside finally block - ALWAYS executes End
Example 2: finally with Exception:
public class FinallyExample2 { public static void main(String[] args) { System.out.println("Start"); try { System.out.println("Inside try block"); int result = 10 / 0; // ArithmeticException! System.out.println("This won't execute"); } catch (ArithmeticException e) { System.out.println("Inside catch block: " + e.getMessage()); } finally { System.out.println("Inside finally block - ALWAYS executes"); } System.out.println("End"); } } Output: Start Inside try block Inside catch block: / by zero Inside finally block - ALWAYS executes End
Example 3: finally with return Statement:
public class FinallyWithReturn { public static int testFinally() { try { System.out.println("Inside try"); return 10; // Will return, but finally executes first! } catch (Exception e) { System.out.println("Inside catch"); return 20; } finally { System.out.println("Inside finally - executes even with return!"); } } public static void main(String[] args) { int result = testFinally(); System.out.println("Returned value: " + result); } } Output: Inside try Inside finally - executes even with return! Returned value: 10
Practical Use Case - File Handling:
import java.io.*; public class FinallyFileExample { public static void main(String[] args) { BufferedReader br = null; try { br = new BufferedReader(new FileReader("data.txt")); String line = br.readLine(); System.out.println("Data: " + line); // Some processing that may throw exception int num = Integer.parseInt(line); System.out.println("Number: " + num); } catch (FileNotFoundException e) { System.out.println("File not found"); } catch (IOException e) { System.out.println("Error reading file"); } catch (NumberFormatException e) { System.out.println("Invalid number"); } finally { // ALWAYS close the file - cleanup code System.out.println("Closing file..."); try { if (br != null) { br.close(); System.out.println("File closed successfully"); } } catch (IOException e) { System.out.println("Error closing file"); } } System.out.println("Program ends"); } }
💡 When finally doesn't execute:
Only 4 rare cases:
• System.exit() called
• JVM crash
• Thread death
• Infinite loop in try/catch
💡 finally vs try-with-resources:
Java 7+ provides try-with-resources that auto-closes resources:
// Old way with finally BufferedReader br = null; try { br = new BufferedReader(new FileReader("file.txt")); } finally { if (br != null) br.close(); } // New way - try-with-resources (preferred) try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) { // Use br - automatically closed! }

5. The throws Clause

📖 What is throws keyword?
Declares in method signature that a method can throw exceptions. Delegates handling to the caller.

throw vs throws:
throw: Actually throws an exception (inside method body)
throws: Declares possible exceptions (in method signature)
Syntax:
returnType methodName(parameters) throws Exception1, Exception2 { // Method body // May throw Exception1 or Exception2 }
Example 1: Basic throws:
import java.io.*; class FileProcessor { // Declares that this method can throw IOException public static void readFile(String filename) throws IOException { FileReader fr = new FileReader(filename); // May throw IOException BufferedReader br = new BufferedReader(fr); String line = br.readLine(); System.out.println("Data: " + line); br.close(); } } public class ThrowsExample1 { public static void main(String[] args) { try { // Caller must handle the declared exception FileProcessor.readFile("data.txt"); } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } } }
Example 2: Multiple throws:
class Calculator { // Declares multiple possible exceptions public static int divide(String num1, String num2) throws NumberFormatException, ArithmeticException { int a = Integer.parseInt(num1); // May throw NumberFormatException int b = Integer.parseInt(num2); return a / b; // May throw ArithmeticException } } public class ThrowsExample2 { public static void main(String[] args) { try { int result = Calculator.divide("10", "2"); System.out.println("Result: " + result); result = Calculator.divide("10", "0"); // Will throw } catch (NumberFormatException e) { System.out.println("Invalid number: " + e.getMessage()); } catch (ArithmeticException e) { System.out.println("Math error: " + e.getMessage()); } } } Output: Result: 5 Math error: / by zero
throws in Method Chain:
import java.io.*; class FileHandler { // Method 1 declares exception public static void processFile() throws IOException { readFile(); // Calling method that throws } // Method 2 declares exception public static void readFile() throws IOException { FileReader fr = new FileReader("data.txt"); // Reading code fr.close(); } } public class ThrowsChain { public static void main(String[] args) { try { // Final caller handles the exception FileHandler.processFile(); } catch (IOException e) { System.out.println("File error: " + e.getMessage()); } } }
throw vs throws - Complete Comparison:
Feature throw throws
Purpose Throw exception explicitly Declare possible exceptions
Location Inside method body Method signature
Syntax throw new Exception(); void m() throws Exception
Number One exception at a time Multiple exceptions (comma-separated)
Followed by Exception object Exception class name
Combined Example - throw with throws:
class VoterValidator { // Declares that method can throw exception public static void checkEligibility(int age) throws Exception { if (age < 18) { // Actually throws the exception throw new Exception("Not eligible. Age: " + age); } System.out.println("Eligible to vote. Age: " + age); } } public class ThrowVsThrows { public static void main(String[] args) { try { VoterValidator.checkEligibility(20); // OK VoterValidator.checkEligibility(15); // Will throw } catch (Exception e) { System.out.println("Error: " + e.getMessage()); } } } Output: Eligible to vote. Age: 20 Error: Not eligible. Age: 15
💡 Key Points:
throws is a warning - "this method might throw exception"
• Caller must handle (try-catch) or propagate (throws again)
• Used with checked exceptions (mandatory)
• Optional for unchecked exceptions
• Can declare parent exception to cover multiple child exceptions
🧵 Chapter 3 — Multithreaded Programming
🧵 MULTITHREADING - MIND MAP
What is Thread? → Lightweight process, smallest unit of execution
Life Cycle → New → Runnable → Running → Blocked/Waiting → Terminated
Creating Threads → Extend Thread class OR implement Runnable interface
Priorities → MIN_PRIORITY (1), NORM_PRIORITY (5), MAX_PRIORITY (10)
Synchronization → synchronized keyword, prevents race conditions
Communication → wait(), notify(), notifyAll()
Control → suspend(), resume(), stop() (deprecated - use flags)

1. Multithreading

📖 What is a Thread?
A lightweight subprocess — smallest unit of processing. Has its own call stack but shares memory with other threads.

Multithreading: Executing multiple threads simultaneously to maximize CPU utilization.
Process vs Thread:
Process Thread
Heavyweight Lightweight
Separate memory space Shares memory space
High creation cost Low creation cost
Isolated (no direct sharing) Can share data easily
Example: Multiple programs Example: Multiple tabs in browser
Advantages of Multithreading:
1. Better CPU Utilization: Other threads execute while one waits for I/O.

2. Improved Responsiveness: UI stays responsive during background tasks.

3. Resource Sharing: Threads share memory, reducing overhead.

4. Faster Execution: Parallel execution on multi-core processors.

5. Simplified Structure: Complex applications easier to design with threads.
Simple Thread Example:
class MyThread extends Thread { public void run() { for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + ": " + i); } } } public class ThreadDemo { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.start(); // Starts thread 1 t2.start(); // Starts thread 2 } } Output (may vary - concurrent execution): Thread-0: 1 Thread-1: 1 Thread-0: 2 Thread-1: 2 Thread-0: 3 Thread-1: 3 Thread-0: 4 Thread-1: 4 Thread-0: 5 Thread-1: 5
💡 Key Points:
• Each thread runs independently
• Output order not guaranteed (concurrent execution)
• Threads share same memory space
• Use start() method (NOT run() directly)

2. Life Cycle of Thread

📖 Thread Life Cycle:
A thread goes through various states from creation to termination.

5 States: New, Runnable, Running, Blocked/Waiting, Terminated
💡 Analogy: Like attending an exam — New (registered), Runnable (waiting outside), Running (writing exam), Blocked (waiting for pen), Terminated (submitted).
Thread States in Detail:
1. New (Born) State:
Thread created but not yet started.
How: Thread t = new Thread();
2. Runnable State:
Thread ready to run, waiting for CPU time.
How: t.start();
3. Running State:
Thread executing its run() method - CPU allocated.
How: Thread scheduler selects from runnable pool.
4. Blocked/Waiting State:
Thread temporarily inactive, waiting for resource or I/O.
How: sleep(), wait(), I/O operations, waiting for lock
5. Terminated (Dead) State:
Thread finished execution or stopped. Cannot be restarted.
How: run() method completes, or stop() called
Thread Life Cycle Diagram: NEW | | start() ↓ RUNNABLE ←──────────────┐ | | | Scheduler picks | I/O complete, notify() ↓ | sleep() time up RUNNING | | | | sleep(), wait() | | I/O request | ↓ | BLOCKED/WAITING ─────────┘ RUNNING | | run() completes ↓ TERMINATED
Thread State Methods:
// Check thread state Thread t = new Thread(); System.out.println(t.getState()); // NEW t.start(); System.out.println(t.getState()); // RUNNABLE // Thread state constants: Thread.State.NEW Thread.State.RUNNABLE Thread.State.BLOCKED Thread.State.WAITING Thread.State.TIMED_WAITING Thread.State.TERMINATED
Complete Life Cycle Example:
class LifeCycleThread extends Thread { public void run() { System.out.println("State: RUNNING"); try { Thread.sleep(2000); // Goes to TIMED_WAITING System.out.println("Woke up from sleep"); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("About to terminate"); } } public class ThreadLifeCycle { public static void main(String[] args) throws InterruptedException { LifeCycleThread t = new LifeCycleThread(); System.out.println("After creation: " + t.getState()); // NEW t.start(); System.out.println("After start(): " + t.getState()); // RUNNABLE Thread.sleep(100); System.out.println("While sleeping: " + t.getState()); // TIMED_WAITING t.join(); // Wait for thread to complete System.out.println("After completion: " + t.getState()); // TERMINATED } } Output: After creation: NEW After start(): RUNNABLE State: RUNNING While sleeping: TIMED_WAITING Woke up from sleep About to terminate After completion: TERMINATED

3. Creating a Thread and Multiple Threads

📖 Two Ways to Create Threads:
1. Extend Thread class: Override run() method
2. Implement Runnable interface: Define run() method

Which to use? Runnable is preferred (allows extending other classes, better design).
Method 1: Extending Thread Class:
// Step 1: Create class extending Thread class MyThread extends Thread { // Step 2: Override run() method public void run() { for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + ": " + i); try { Thread.sleep(500); // Pause 500ms } catch (InterruptedException e) { e.printStackTrace(); } } } } public class ThreadExample1 { public static void main(String[] args) { // Step 3: Create thread objects MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); // Step 4: Start threads t1.start(); t2.start(); } } Output: Thread-0: 1 Thread-1: 1 Thread-0: 2 Thread-1: 2 ...
Method 2: Implementing Runnable Interface:
// Step 1: Create class implementing Runnable class MyRunnable implements Runnable { // Step 2: Implement run() method public void run() { for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + ": " + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class RunnableExample { public static void main(String[] args) { // Step 3: Create Runnable objects MyRunnable r = new MyRunnable(); // Step 4: Create Thread objects with Runnable Thread t1 = new Thread(r); Thread t2 = new Thread(r); // Step 5: Start threads t1.start(); t2.start(); } }
Feature Extending Thread Implementing Runnable
Inheritance Cannot extend other class Can extend other class
Code Simpler syntax Slightly more code
Object sharing Separate objects Same Runnable shared
Recommended ❌ Less flexible ✅ Preferred approach
Creating Multiple Threads:
class Counter implements Runnable { private String name; public Counter(String name) { this.name = name; } public void run() { for (int i = 1; i <= 3; i++) { System.out.println(name + ": Count " + i); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class MultipleThreads { public static void main(String[] args) { // Create multiple threads Thread t1 = new Thread(new Counter("Thread-A")); Thread t2 = new Thread(new Counter("Thread-B")); Thread t3 = new Thread(new Counter("Thread-C")); // Start all threads t1.start(); t2.start(); t3.start(); } } Output (interleaved): Thread-A: Count 1 Thread-B: Count 1 Thread-C: Count 1 Thread-A: Count 2 Thread-B: Count 2 Thread-C: Count 2 Thread-A: Count 3 Thread-B: Count 3 Thread-C: Count 3
💡 Important Rules:
• Call start() to begin execution (NOT run())
• run() is called automatically by JVM
• Cannot call start() twice on same thread
• Each thread needs separate Thread object

4. Priorities

📖 Thread Priority:
Thread priority determines scheduling order. Higher priority threads get preference, but it's not guaranteed.

Priority Range: 1 (MIN) to 10 (MAX), Default is 5 (NORM)
💡 Analogy: Like a hospital ER — critical patients (priority 10) treated before minor injuries (priority 1).
Priority Constants:
Thread.MIN_PRIORITY = 1 // Lowest priority Thread.NORM_PRIORITY = 5 // Default priority Thread.MAX_PRIORITY = 10 // Highest priority
Setting Thread Priority:
class PriorityThread extends Thread { public void run() { System.out.println(Thread.currentThread().getName() + " - Priority: " + Thread.currentThread().getPriority()); } } public class PriorityDemo { public static void main(String[] args) { PriorityThread t1 = new PriorityThread(); PriorityThread t2 = new PriorityThread(); PriorityThread t3 = new PriorityThread(); // Set priorities t1.setPriority(Thread.MIN_PRIORITY); // 1 t2.setPriority(Thread.NORM_PRIORITY); // 5 t3.setPriority(Thread.MAX_PRIORITY); // 10 // Start threads t1.start(); t2.start(); t3.start(); } } Output: Thread-2 - Priority: 10 Thread-1 - Priority: 5 Thread-0 - Priority: 1
Complete Priority Example:
class Task implements Runnable { private String name; public Task(String name) { this.name = name; } public void run() { for (int i = 1; i <= 3; i++) { System.out.println(name + " (Priority: " + Thread.currentThread().getPriority() + ") - Count: " + i); } } } public class PriorityExample { public static void main(String[] args) { Thread highPriority = new Thread(new Task("HIGH"), "High-Priority"); Thread normalPriority = new Thread(new Task("NORM"), "Normal-Priority"); Thread lowPriority = new Thread(new Task("LOW"), "Low-Priority"); // Set priorities highPriority.setPriority(10); normalPriority.setPriority(5); lowPriority.setPriority(1); // Start all lowPriority.start(); normalPriority.start(); highPriority.start(); } } Output (high priority likely executes first): HIGH (Priority: 10) - Count: 1 HIGH (Priority: 10) - Count: 2 HIGH (Priority: 10) - Count: 3 NORM (Priority: 5) - Count: 1 NORM (Priority: 5) - Count: 2 NORM (Priority: 5) - Count: 3 LOW (Priority: 1) - Count: 1 LOW (Priority: 1) - Count: 2 LOW (Priority: 1) - Count: 3
💡 Important Notes:
• Priority is a hint to scheduler, not guaranteed
• Default priority inherited from parent thread
• Must be between 1-10 (throws IllegalArgumentException otherwise)
• Behavior varies across JVM implementations

5. Synchronization

📖 What is Synchronization?
Controls access to shared resources — only one thread can access synchronized code at a time.

Why? Prevents race conditions and data corruption.
Problem: Race Condition (Without Synchronization):
class Counter { private int count = 0; public void increment() { count++; // NOT thread-safe! } public int getCount() { return count; } } class CounterThread extends Thread { private Counter counter; public CounterThread(Counter counter) { this.counter = counter; } public void run() { for (int i = 0; i < 1000; i++) { counter.increment(); } } } public class RaceConditionDemo { public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Thread t1 = new CounterThread(counter); Thread t2 = new CounterThread(counter); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("Expected: 2000"); System.out.println("Actual: " + counter.getCount()); // May be less! } } Output (unpredictable): Expected: 2000 Actual: 1847 // WRONG! Lost updates due to race condition
Solution: Synchronized Method:
class SynchronizedCounter { private int count = 0; // synchronized keyword - only one thread at a time public synchronized void increment() { count++; } public int getCount() { return count; } } class SyncThread extends Thread { private SynchronizedCounter counter; public SyncThread(SynchronizedCounter counter) { this.counter = counter; } public void run() { for (int i = 0; i < 1000; i++) { counter.increment(); } } } public class SynchronizationDemo { public static void main(String[] args) throws InterruptedException { SynchronizedCounter counter = new SynchronizedCounter(); Thread t1 = new SyncThread(counter); Thread t2 = new SyncThread(counter); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("Expected: 2000"); System.out.println("Actual: " + counter.getCount()); // Always correct! } } Output: Expected: 2000 Actual: 2000 // CORRECT! synchronized ensures thread safety
Synchronized Block:
Synchronized Block: Synchronizes only a specific code section instead of the entire method.
class BankAccount { private int balance = 1000; public void withdraw(int amount) { System.out.println(Thread.currentThread().getName() + " attempting withdrawal"); // Only critical section is synchronized synchronized(this) { if (balance >= amount) { System.out.println(Thread.currentThread().getName() + " proceeding with withdrawal"); balance -= amount; System.out.println(Thread.currentThread().getName() + " completed. Balance: " + balance); } else { System.out.println(Thread.currentThread().getName() + " insufficient funds"); } } } } public class SyncBlockDemo { public static void main(String[] args) { BankAccount account = new BankAccount(); Thread t1 = new Thread(() -> account.withdraw(600), "Thread-1"); Thread t2 = new Thread(() -> account.withdraw(600), "Thread-2"); t1.start(); t2.start(); } } Output: Thread-1 attempting withdrawal Thread-2 attempting withdrawal Thread-1 proceeding with withdrawal Thread-1 completed. Balance: 400 Thread-2 insufficient funds
💡 Synchronization Best Practices:
• Use only when necessary (performance cost)
• Synchronize smallest code section possible
• Avoid nested synchronized blocks (deadlock risk)
• Static synchronized methods lock on class object
• Instance synchronized methods lock on 'this' object

6. Inter Thread Communication

📖 Inter Thread Communication:
Mechanism for threads to coordinate using wait(), notify(), and notifyAll() methods. Used in producer-consumer problems.

Three methods: wait() - release lock & wait, notify() - wake one thread, notifyAll() - wake all threads
💡 Analogy: Chef (producer) makes food, waiter (consumer) serves it. Waiter wait()s if no food; chef notify()s when ready.
wait(), notify(), notifyAll() Methods:
Method Purpose Must be called in
wait() Release lock, wait for notification synchronized block/method
notify() Wake up one waiting thread synchronized block/method
notifyAll() Wake up all waiting threads synchronized block/method
Producer-Consumer Problem:
class SharedQueue { private int data; private boolean available = false; // Producer produces data public synchronized void produce(int value) { while (available) { try { wait(); // Wait if data already available } catch (InterruptedException e) { e.printStackTrace(); } } data = value; available = true; System.out.println("Produced: " + value); notify(); // Notify consumer } // Consumer consumes data public synchronized int consume() { while (!available) { try { wait(); // Wait if no data available } catch (InterruptedException e) { e.printStackTrace(); } } available = false; System.out.println("Consumed: " + data); notify(); // Notify producer return data; } } class Producer extends Thread { private SharedQueue queue; public Producer(SharedQueue queue) { this.queue = queue; } public void run() { for (int i = 1; i <= 5; i++) { queue.produce(i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Consumer extends Thread { private SharedQueue queue; public Consumer(SharedQueue queue) { this.queue = queue; } public void run() { for (int i = 1; i <= 5; i++) { queue.consume(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class ProducerConsumerDemo { public static void main(String[] args) { SharedQueue queue = new SharedQueue(); Producer producer = new Producer(queue); Consumer consumer = new Consumer(queue); producer.start(); consumer.start(); } } Output: Produced: 1 Consumed: 1 Produced: 2 Consumed: 2 Produced: 3 Consumed: 3 Produced: 4 Consumed: 4 Produced: 5 Consumed: 5
💡 Important Rules:
• wait(), notify(), notifyAll() are in Object class (not Thread)
• Must be called inside synchronized context
• wait() releases lock; notify() doesn't release lock immediately
• Use notifyAll() when multiple threads waiting
• Always check condition in while loop (spurious wakeups)

7. Suspending, Resuming, and Stopping Threads

📖 Thread Control Methods:
suspend(), resume(), stop() are DEPRECATED (cause deadlocks, inconsistent state).

Safe alternative: Use volatile boolean flags with interrupts.
Why Deprecated Methods are Dangerous:
suspend(): Holds locks → Deadlock risk
resume(): Can't resume if not suspended → Race condition
stop(): Immediately terminates → Inconsistent object state
Safe Alternative: Using Flags:
class ControlledThread extends Thread { private volatile boolean suspended = false; private volatile boolean stopped = false; public void run() { System.out.println("Thread started"); for (int i = 1; i <= 10 && !stopped; i++) { // Check if suspended synchronized(this) { while (suspended) { try { wait(); // Wait until resumed } catch (InterruptedException e) { e.printStackTrace(); } } } System.out.println("Count: " + i); try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println("Thread interrupted"); } } System.out.println("Thread stopped"); } // Safe suspend using flag public void suspendThread() { suspended = true; } // Safe resume using notify public synchronized void resumeThread() { suspended = false; notify(); } // Safe stop using flag public void stopThread() { stopped = true; } } public class ThreadControlDemo { public static void main(String[] args) throws InterruptedException { ControlledThread thread = new ControlledThread(); thread.start(); Thread.sleep(2000); System.out.println("\n--- SUSPENDING THREAD ---"); thread.suspendThread(); Thread.sleep(2000); System.out.println("--- RESUMING THREAD ---\n"); thread.resumeThread(); Thread.sleep(2000); System.out.println("\n--- STOPPING THREAD ---"); thread.stopThread(); } } Output: Thread started Count: 1 Count: 2 Count: 3 --- SUSPENDING THREAD --- --- RESUMING THREAD --- Count: 4 Count: 5 Count: 6 --- STOPPING THREAD --- Thread stopped
Using interrupt() for Stopping:
class InterruptibleThread extends Thread { public void run() { try { for (int i = 1; i <= 10; i++) { if (Thread.interrupted()) { System.out.println("Thread interrupted at count: " + i); return; // Exit gracefully } System.out.println("Count: " + i); Thread.sleep(500); } } catch (InterruptedException e) { System.out.println("Thread interrupted during sleep"); } } } public class InterruptDemo { public static void main(String[] args) throws InterruptedException { InterruptibleThread thread = new InterruptibleThread(); thread.start(); Thread.sleep(2000); System.out.println("\nInterrupting thread...\n"); thread.interrupt(); // Safe way to signal stop } } Output: Count: 1 Count: 2 Count: 3 Interrupting thread... Thread interrupted during sleep
Comparison Table:
Method Old (Deprecated) Safe Alternative
Suspend suspend() volatile boolean + wait()
Resume resume() notify()/notifyAll()
Stop stop() volatile boolean flag or interrupt()
💡 Best Practices:
• NEVER use suspend(), resume(), stop()
• Use volatile boolean flags for control
• Use interrupt() for cooperative cancellation
• Always handle InterruptedException properly
• Check flags/interrupts frequently in loops
🎯 Why volatile keyword:
• Ensures variable changes are immediately visible to all threads
• Prevents thread-local caching of variables
• Essential for boolean flags in multithreading
Quick Revision — Last Minute Notes
📌 How to Use: Read this 5-10 minutes before exam. Contains all important points in condensed form. Focus on tables, comparisons, and key topics!

📦 Built-in Packages (2M)

6 Built-in Packages:
1. java.lang — Auto-imported. String, Math, System, Object
2. java.util — ArrayList, HashMap, Scanner, Date
3. java.io — FileReader, FileWriter, BufferedReader, PrintWriter
4. java.awt — GUI: Frame, Button, Label, TextField
5. java.math — BigInteger, BigDecimal
6. java.sql — Connection, Statement, ResultSet
Exam Trick: java.lang is auto-imported, rest need import statement

📁 User-Defined Package (2M)

Steps to create:
1. Write package name; at top of file
2. Compile: javac -d . FileName.java
3. Import: import packagename.ClassName;
package mypack; public class Calculator { public int add(int a, int b) { return a + b; } } // Compile: javac -d . Calculator.java // Use: import mypack.Calculator;

🔑 4 Ways to Access Package

1. Specific import: import java.util.ArrayList;
2. Wildcard import: import java.util.*;
3. Fully qualified: java.util.ArrayList list = new java.util.ArrayList();
4. Static import: import static java.lang.Math.*; — use sqrt(), pow() directly

🔄 Byte Stream vs Character Stream (IMP!)

Byte Stream | Character Stream ------------------------ | ----------------------- Binary data (8-bit) | Text data (16-bit Unicode) InputStream/OutputStream | Reader/Writer Images, videos, any file | Text files only FileInputStream | FileReader FileOutputStream | FileWriter No encoding | Unicode encoding
Trick: Byte = Binary (B-B), Character = Text (C-T)

📖 BufferedReader & PrintWriter (5M)

BufferedReader: Reads text efficiently by buffering characters
• Reads in chunks (not char by char) — faster
readLine() reads entire line at once

PrintWriter: Writes formatted text to file
println() writes line with newline
• Auto-flushes with constructor flag
// Read file BufferedReader br = new BufferedReader(new FileReader("data.txt")); String line; while ((line = br.readLine()) != null) { System.out.println(line); } br.close(); // Write file PrintWriter pw = new PrintWriter(new FileWriter("out.txt")); pw.println("Hello World"); pw.close();

⚠️ What is Exception? (2M)

Exception: An unwanted event during execution that disrupts normal program flow.

Exception Hierarchy:
Object → Throwable → Error (serious) + Exception
Exception → Checked (compile-time) + Unchecked (runtime)

🔍 Checked vs Unchecked (IMP!)

Checked | Unchecked --------------------------- | --------------------------- Compile-time check | Runtime only Must handle (try-catch) | Optional to handle External factors | Programming errors IOException | NullPointerException SQLException | ArithmeticException FileNotFoundException | ArrayIndexOutOfBounds
Trick: Checked = Compiler forces you, Unchecked = Your mistake (coding error)

📋 6 Common Exceptions (2M)

1. NullPointerException — null object pe method call
2. ArithmeticException — division by zero (10/0)
3. ArrayIndexOutOfBoundsException — invalid array index
4. NumberFormatException — "abc" to int conversion
5. FileNotFoundException — file not found (Checked)
6. IOException — I/O operation fail (Checked)

🛡️ try-catch-finally (5M)

try: Risky code jo exception throw kar sakta hai
catch: Exception handle karta hai — program crash nahi hota
finally: Hamesha execute hota hai — cleanup (file/connection close)
Multi-catch: catch (IOException | SQLException e)
try { int result = 10 / 0; // ArithmeticException } catch (ArithmeticException e) { System.out.println("Error: " + e.getMessage()); } finally { System.out.println("Always executes - cleanup"); } // Output: Error: / by zero → Always executes - cleanup
Remember: finally always executes, even with return statement. Only System.exit(0) skips it.

🎯 throw vs throws (IMP!)

throw | throws --------------------------- | --------------------------- Inside method body | Method signature Throw exception explicitly | Declare exception Exception object | Exception class name One at a time | Multiple allowed throw new Exception("msg") | void m() throws IOException Action (actually fenkna) | Declaration (warning dena)
Example: void checkAge(int age) throws InvalidAgeException { if (age < 18) { throw new InvalidAgeException("Must be 18+"); } }
Memory Trick:
throw = action (doing it) — method ke andar
throws = declaration (warning) — method signature mein

🔧 Custom Exception (5M)

Steps:
1. Create class extending Exception
2. Add constructor with super(message)
3. Use throw to throw it, throws to declare it
class InvalidAgeException extends Exception { InvalidAgeException(String msg) { super(msg); } } // Usage: throw new InvalidAgeException("Age must be 18+");

🧵 Multithreading (2M)

Definition: Executing multiple threads simultaneously within a single program.

Advantages:
✅ Better CPU utilization (no idle time)
✅ Improved responsiveness
✅ Resource sharing between threads
✅ Faster execution of independent tasks

🔄 Thread Life Cycle (5M)

5 States:
1. New — Thread object created, not started
2. Runnable — start() called, waiting for CPU
3. Running — run() method executing
4. Blocked/Waiting — sleep(), wait(), I/O
5. Terminated — run() completed or exception
New → start() → Runnable → CPU assigned → Running Running → sleep()/wait() → Blocked/Waiting Running → run() done → Terminated Blocked → notify()/timeout → Runnable

🚀 Creating Threads — 2 Ways (IMP!)

Extend Thread class | Implement Runnable ------------------------ | ----------------------- class A extends Thread | class A implements Runnable Override run() | Override run() new A().start() | new Thread(new A()).start() Cannot extend other class| Can extend other class Less flexible | More flexible (preferred)
// Way 1: Extend Thread class MyThread extends Thread { public void run() { System.out.println("Thread running"); } } new MyThread().start(); // Way 2: Implement Runnable (Preferred) class MyTask implements Runnable { public void run() { System.out.println("Task running"); } } new Thread(new MyTask()).start();
Why Runnable preferred? Java doesn't support multiple inheritance with classes. Runnable allows extending another class too.

⚡ Thread Priorities (2M)

Range: 1 to 10 (higher = more chance, not guaranteed)

MIN_PRIORITY = 1
NORM_PRIORITY = 5 (default)
MAX_PRIORITY = 10
t.setPriority(Thread.MAX_PRIORITY); // Set to 10 int p = t.getPriority(); // Get priority

🔒 Synchronization (5M)

Why needed? Multiple threads accessing shared resource → race condition → data corruption

Solution: synchronized keyword — only one thread at a time
// Synchronized Method synchronized void increment() { count++; } // Synchronized Block synchronized(obj) { // critical section - only one thread enters }

💬 Inter-Thread Communication (IMP!)

wait() | notify() | notifyAll() ----------- | ---------------- | ---------------- Release lock | Wake one thread | Wake all threads Thread waits | Specific thread | All waiting threads Must be in synchronized block/method
Used in: Producer-Consumer problem. Producer produces → notify() → Consumer consumes → wait() for more.

🎮 Thread Control (2M)

Deprecated: suspend(), resume(), stop() — deadlock risk!
Safe Way: Use volatile boolean flags
volatile boolean running = true;
while(running) { // work }
Stop: running = false;

⚠️ Common Exam Mistakes

❌ Not handling checked exceptions (IOException, SQLException)
❌ Empty catch blocks — bad practice
❌ Confusing throw vs throws
❌ Using deprecated methods (stop, suspend, resume)
❌ Forgetting to close streams (use finally/try-with-resources)
❌ Not calling start() — calling run() directly doesn't create thread
❌ Using wait/notify outside synchronized block
❌ Confusing Byte Stream with Character Stream

💻 Must Practice Programs

1. Create and use user-defined package
2. File read using BufferedReader
3. File write using PrintWriter
4. try-catch-finally with multiple catch
5. Custom exception creation and usage
6. throw vs throws demonstration
7. Create thread by extending Thread class
8. Create thread by implementing Runnable
9. Thread synchronization (synchronized counter)
10. Producer-Consumer using wait/notify

✅ Pre-Exam Checklist

☑ 6 built-in packages with examples
☑ User-defined package creation steps
☑ 4 ways to access packages
☑ Byte vs Character stream table
☑ BufferedReader & PrintWriter programs
☑ Exception hierarchy
☑ Checked vs Unchecked table
☑ 6 common exceptions
☑ try-catch-finally with multiple catch
☑ throw vs throws table
☑ Custom exception program
☑ Thread lifecycle (5 states)
☑ Thread creation (2 ways) with programs
☑ Priorities (1, 5, 10)
☑ Synchronization (method + block)
☑ wait/notify/notifyAll
☑ Volatile flags for safe thread control

🎯 Exam Strategy

2 Mark Questions:
• 2-3 sentences or 3-4 points
• Time: 3-4 minutes max
• Add example if time permits

5 Mark Questions:
• Definition + Explanation + Code + Output
• Time: 7-8 minutes
• Draw comparison tables when asked "differentiate"

Code Questions:
• Write proper syntax with import statements
• Add comments for explanation
• Show expected output
• Use meaningful variable names
🌟 All the Best!
Packages, Exceptions aur Multithreading — teeno chapters practice-based hain! Tables yaad karo (Byte vs Char, Checked vs Unchecked, throw vs throws), programs practice karo, aur exam ready ho! 💪☕
Important Questions — Exam-Focused
📌 About This Section:
Contains 15 important questions (10 × 2M + 5 × 5M) covering all 3 chapters. Click any question to expand the answer.

📝 Section A — 2 Marks Questions (10)

2M
Q1. What is a package in Java? Why do we use packages?
Answer:

Package: A namespace that organizes related classes and interfaces into a single unit.

Why we use packages:
1. Avoid naming conflicts — two classes with same name can exist in different packages
2. Access control — provides public, protected, default access levels
3. Better organization — related classes grouped together
4. Code reusability — import and reuse anywhere

Example: import java.util.ArrayList;
2M
Q2. Differentiate between Byte Stream and Character Stream.
Answer:

Byte StreamCharacter Stream
Handles binary data (8-bit)Handles text data (16-bit Unicode)
InputStream / OutputStreamReader / Writer
Used for images, videos, any fileUsed for text files only
Example: FileInputStreamExample: FileReader
2M
Q3. List any four built-in packages in Java with their use.
Answer:

1. java.lang — Core classes: String, Math, System (auto-imported)
2. java.util — Utility classes: ArrayList, HashMap, Scanner
3. java.io — Input/Output: FileReader, FileWriter, BufferedReader
4. java.sql — Database access: Connection, Statement, ResultSet
2M
Q4. What is BufferedReader? Why is it preferred over FileReader?
Answer:

BufferedReader: A character stream class that reads text from input stream by buffering characters for efficient reading.

Why preferred:
• Reads data in chunks (not character by character) — faster I/O
• Provides readLine() method to read entire line at once
• Reduces number of I/O operations, improving performance
2M
Q5. Differentiate between checked and unchecked exceptions.
Answer:

Checked ExceptionUnchecked Exception
Checked at compile-timeOccurs at runtime
Must handle (try-catch or throws)Handling is optional
External factors (file, network)Programming errors
IOException, SQLExceptionNullPointerException, ArithmeticException
2M
Q6. What is the use of finally block? Does it always execute?
Answer:

finally block: A block that always executes after try-catch, regardless of whether exception occurred or not.

Use: Cleanup operations — closing files, database connections, releasing resources.

Yes, it always executes — even when there is a return statement in try or catch block. Only exception: System.exit(0) will skip finally.
2M
Q7. Differentiate between throw and throws keyword.
Answer:

throwthrows
Used to throw an exception explicitlyUsed to declare exceptions
Written inside method bodyWritten in method signature
throw new Exception("msg");void m() throws IOException
Can throw one exception at a timeCan declare multiple exceptions
2M
Q8. What is multithreading? List its advantages.
Answer:

Multithreading: A process of executing multiple threads simultaneously within a single program to maximize CPU utilization.

Advantages:
• Better CPU utilization (no idle time)
• Improved application responsiveness
• Resource sharing between threads
• Faster execution of independent tasks
2M
Q9. What is synchronization? Why is it needed?
Answer:

Synchronization: A mechanism to control access of shared resources by multiple threads so that only one thread can access the resource at a time.

Why needed:
• Prevents race conditions (two threads modifying same data)
• Ensures data consistency and thread safety
• Uses synchronized keyword on method or block
2M
Q10. Explain thread priorities with constants.
Answer:

Thread Priority: An integer value (1-10) that determines the order in which threads get CPU time. Higher priority = more chance of execution (not guaranteed).

Constants:
Thread.MIN_PRIORITY = 1
Thread.NORM_PRIORITY = 5 (default)
Thread.MAX_PRIORITY = 10

Set: t.setPriority(Thread.MAX_PRIORITY);

📝 Section B — 5 Marks Questions (5)

5M
Q1. Write a program to create and use a user-defined package. Explain different ways to access packages.
Answer:

4 Ways to Access Package:
1. Specific import: import mypack.Calculator;
2. Wildcard import: import mypack.*;
3. Fully qualified: mypack.Calculator calc = new mypack.Calculator();
4. Static import: import static java.lang.Math.*;

Program:
// File: Calculator.java package mypackage.utils; public class Calculator { public int add(int a, int b) { return a + b; } public int subtract(int a, int b) { return a - b; } public int multiply(int a, int b) { return a * b; } } // Compile: javac -d . Calculator.java
// File: Main.java import mypackage.utils.Calculator; public class Main { public static void main(String[] args) { Calculator calc = new Calculator(); System.out.println("10 + 5 = " + calc.add(10, 5)); System.out.println("10 - 5 = " + calc.subtract(10, 5)); System.out.println("10 * 5 = " + calc.multiply(10, 5)); } } // Output: 10 + 5 = 15, 10 - 5 = 5, 10 * 5 = 50
5M
Q2. Write programs to read and write files using BufferedReader and PrintWriter.
Answer:

Reading file using BufferedReader:
import java.io.*; public class FileRead { public static void main(String[] args) { try { FileReader fr = new FileReader("data.txt"); BufferedReader br = new BufferedReader(fr); String line; while ((line = br.readLine()) != null) { System.out.println(line); } br.close(); } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } } }
Writing file using PrintWriter:
import java.io.*; public class FileWrite { public static void main(String[] args) { try { FileWriter fw = new FileWriter("output.txt"); PrintWriter pw = new PrintWriter(fw); pw.println("Student Records"); pw.println("Name: Raj"); pw.println("Marks: 85"); pw.close(); System.out.println("File written successfully"); } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } } }
5M
Q3. Explain exception handling using try-catch-finally with program. Also write a custom exception program.
Answer:

try: Contains risky code that may throw exception
catch: Handles the exception — prevents program crash
finally: Always executes — used for cleanup (closing files, connections)

Program 1: try-catch-finally
public class TryCatchFinally { public static void main(String[] args) { try { System.out.println("Try block"); int result = 10 / 0; // ArithmeticException } catch (ArithmeticException e) { System.out.println("Catch: " + e.getMessage()); } finally { System.out.println("Finally - always executes"); } System.out.println("Program continues normally"); } } // Output: Try block → Catch: / by zero → Finally → Program continues
Program 2: Custom Exception
class InvalidAgeException extends Exception { public InvalidAgeException(String msg) { super(msg); } } public class CustomExceptionDemo { static void checkAge(int age) throws InvalidAgeException { if (age < 18) throw new InvalidAgeException("Age must be 18+. Given: " + age); System.out.println("Age verified: " + age); } public static void main(String[] args) { try { checkAge(20); // OK checkAge(15); // Exception thrown } catch (InvalidAgeException e) { System.out.println("Error: " + e.getMessage()); } } } // Output: Age verified: 20 → Error: Age must be 18+. Given: 15
5M
Q4. Explain thread life cycle. Write programs to create thread using Thread class and Runnable interface.
Answer:

Thread Life Cycle (5 States):
1. New: Thread object created, not started
2. Runnable: start() called, waiting for CPU
3. Running: run() method executing
4. Blocked/Waiting: Paused due to sleep(), wait(), or I/O
5. Terminated: run() completed or exception occurred

Way 1 — Extending Thread class:
class MyThread extends Thread { public void run() { for (int i = 1; i <= 5; i++) System.out.println(getName() + ": " + i); } } public class ThreadDemo1 { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.start(); t2.start(); } }
Way 2 — Implementing Runnable (Preferred):
class MyTask implements Runnable { public void run() { for (int i = 1; i <= 5; i++) System.out.println(Thread.currentThread().getName() + ": " + i); } } public class ThreadDemo2 { public static void main(String[] args) { Thread t1 = new Thread(new MyTask(), "Thread-A"); Thread t2 = new Thread(new MyTask(), "Thread-B"); t1.start(); t2.start(); } }
Why Runnable preferred? Java doesn't support multiple inheritance with classes. Implementing Runnable allows the class to extend another class too.
5M
Q5. Write a program demonstrating thread synchronization. Explain inter-thread communication with wait() and notify().
Answer:

Synchronization: Prevents race conditions by allowing only one thread to access shared resource at a time using synchronized keyword.

Program: Synchronized Counter
class Counter { private int count = 0; synchronized void increment() { count++; } int getCount() { return count; } } public class SyncDemo { public static void main(String[] args) throws Exception { Counter c = new Counter(); Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) c.increment(); }); Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) c.increment(); }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("Count: " + c.getCount()); // Output: Count: 2000 (correct with synchronized) } }
Inter-Thread Communication:
wait(): Thread releases lock and waits until notified
notify(): Wakes up one waiting thread
notifyAll(): Wakes up all waiting threads
All three must be called inside synchronized block/method.

Producer-Consumer Example:
class SharedBuffer { int data; boolean hasData = false; synchronized void produce(int val) throws InterruptedException { while (hasData) wait(); data = val; hasData = true; System.out.println("Produced: " + val); notify(); } synchronized int consume() throws InterruptedException { while (!hasData) wait(); hasData = false; System.out.println("Consumed: " + data); notify(); return data; } }