5.1 Basic Exceptions and Proper Use of Exceptions
In programming, an exception is a situation that leads to an unexpected behavior [cite: 1814]. This can occur in many different ways, such as dividing by zero or attempting to open a file that does not exist [cite: 1815]. These exceptions, if not handled properly, can cause serious issues such as data loss, incorrect calculations, or even a complete system crash [cite: 1816, 1817]. Run-time errors are not exactly programming errors, but it is important to handle them so that a program can continue to function without disrupting its natural flow [cite: 1830].
Proper Use and Best Practices:
- Use try...catch blocks to catch exceptions [cite: 1836].
- Use specific exceptions in the catch block rather than catching generic exceptions [cite: 1836].
- Avoid empty catch blocks, as they silently swallow errors [cite: 1836].
- Avoid overusing checked exceptions [cite: 1836].
5.2 Exception Hierarchy
In Java, all exception and error types are subclasses of the Throwable class, which is the base of the hierarchy. The hierarchy splits into two main branches: Error and Exception.
java.lang.Throwable
├── Error (Unrecoverable conditions like OutOfMemoryError, VirtualMachineError)
└── Exception (Recoverable conditions that programs should catch)
├── IOException (Checked exception)
├── SQLException (Checked exception)
└── RuntimeException (Unchecked exceptions)
├── ArithmeticException
├── NullPointerException
└── ArrayIndexOutOfBoundsException
5.3 Exception Handling Keywords
Java uses five keywords for managing exceptions:
try
A block used to place code that might throw an exception [cite: 1732]. It must be followed by either a catch or finally block.
catch
A block defining how to handle specific exceptions that occur in the try block [cite: 1732].
finally
A block that executes regardless of whether an exception is thrown or handled. It is typically used to close resources.
throw
Used to explicitly throw an exception object from within a method or block of code.
throws
Used in a method signature to declare that the method might throw one or more exceptions, passing the responsibility to the caller.
5.4 Java's Built-in Exceptions
Java provides several built-in exceptions to handle common errors. They are categorized into Checked and Unchecked exceptions:
- Checked Exceptions: Verified by the compiler at compile-time. Examples include IOException (occurs when an attempt to open a file fails) [cite: 1818] and FileNotFoundException [cite: 1822].
- Unchecked Exceptions: Not checked at compile-time (subclasses of RuntimeException). Examples include ArithmeticException (e.g., dividing by zero) and ArrayIndexOutOfBoundsException.
5.5 User-Defined Exceptions
You can create custom exceptions by extending the Exception class (for checked exceptions) or the RuntimeException class (for unchecked exceptions).
// Creating a custom exception
class InvalidAgeException extends Exception {
public InvalidAgeException(String message) {
super(message);
}
}
// Using the custom exception
public class TestCustomException {
static void validateAge(int age) throws InvalidAgeException {
if (age < 18) {
throw new InvalidAgeException("Age is not valid for voting.");
} else {
System.out.println("Welcome to vote!");
}
}
}
5.6 Multithreading Basics
Multithreading is a Java feature that allows concurrent execution of two or more parts of a program for maximum utilization of the CPU. Each part of such a program is called a thread.
- Thread: A lightweight sub-process, the smallest unit of processing.
- Benefits: It doesn't block the user because threads are independent. It saves time by performing multiple operations at the same time.
- Lifecycle of a Thread: New → Runnable → Running → Non-Runnable (Blocked/Waiting) → Terminated.
5.7 Thread Class and Runnable Interface
In Java, there are two primary ways to create a thread:
- By extending the Thread class: You extend the Thread class and override its run() method.
- By implementing the Runnable interface: You implement the Runnable interface, override run(), and pass the instance to a Thread object. (Preferred, because Java doesn't support multiple inheritance, so implementing an interface leaves the class free to extend another class).
// Method 1: Extending Thread
class MyThread extends Thread {
public void run() {
System.out.println("Thread is running.");
}
}
// Usage: MyThread t1 = new MyThread(); t1.start();
// Method 2: Implementing Runnable
class MyRunnable implements Runnable {
public void run() {
System.out.println("Runnable thread is running.");
}
}
// Usage: Thread t2 = new Thread(new MyRunnable()); t2.start();
5.8 Thread Priorities
Each thread has a priority. Priorities are represented by numbers between 1 and 10. The thread scheduler uses these priorities to decide which thread to execute next. The Thread class provides three static constants:
- Thread.MIN_PRIORITY (1)
- Thread.NORM_PRIORITY (5) - Default priority
- Thread.MAX_PRIORITY (10)
You can set a thread's priority using t.setPriority(int newPriority);.
5.9 Thread Synchronization and Inter-thread Communication
Synchronization is the capability to control the access of multiple threads to any shared resource. It prevents thread interference and memory consistency errors. Java provides the synchronized keyword to ensure that only one thread can execute a block of code or a method on a given object at a time.
// Synchronized method
synchronized void printData() {
// Critical section code
}
Inter-thread Communication allows synchronized threads to communicate with each other using the following methods defined in the Object class:
- wait(): Causes the current thread to wait until another thread invokes notify() or notifyAll() for this object.
- notify(): Wakes up a single thread that is waiting on this object's monitor.
- notifyAll(): Wakes up all threads that are waiting on this object's monitor.