Exceptions, the object-oriented programming tool for handling errors, are essential for creating robust and reliable applications.
In this chapter, we will explore the use of this tool to handle errors and unexpected situations, and we will discuss the best practices for managing these events.
# What are Exceptions?
Exceptions are disruptive events that occur during program execution and alter the normal flow of instructions.
In Java, they are objects that encapsulate error information, thrown when something unexpected occurs.
Capturing and handling them properly is crucial to prevent failures and unwanted behaviors.
# Types of Exceptions
Java classifies exceptions into two main categories:
Checked Exceptions
These are exceptions that must be caught or declared in a method. These are situations that, although unexpected, are foreseeable and recoverable, such as a missing file, but the caller is forced to consider the error.
class Checked {
// Declares the possible Exception, so the
// user of the method knows and can catch the error
public static void main(String[] args) throws IOException {
// Creating the File
FileReader file = new FileReader("C:\\file.txt");
BufferedReader fileInput = new BufferedReader(file);
System.out.println(fileInput.readLine());
// Closing the connection with the file
fileInput.close();
}
}
Unchecked Exceptions
These are errors that reflect problems in the code or unforeseeable conditions. It is not mandatory to handle them, and generally, they should be avoided, such as a division-by-zero error. In case of error, it fails and breaks the program.
class Unchecked {
public static void main(String[] args) {
int x = 0;
int y = 10;
int z = y / x; // Throws error
}
}
# Try-Catch Block
The try-catch block is the main structure for handling exceptions in Java. Code that could throw an exception is enclosed within a try block, and through catch blocks, it allows handling different types of exceptions that could be thrown.
try {
// Code that may throw an exception
} catch (ExceptionType1 e) {
// Handle ExceptionType1
} catch (ExceptionType2 e) {
// Handle ExceptionType2
}
# Creating Custom Exceptions
Java allows you to create your own exception classes to represent specific error situations. This is done by extending the Exception or RuntimeException class, depending on whether you want to create a checked or unchecked exception.
public class MyException extends Exception {
public MyException(String message) {
super(message);
}
}
# Practical Example: File Management System
Consider a simple file management system that reads the content of a file.
We must deal with situations such as files that do not exist or do not have read permissions.
FileManager class, throws exceptions:
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class FileManager {
public String readFile(String path) throws MyException {
File file = new File(path);
if (!file.exists()) {
throw new MyException("The file does not exist.");
}
try (FileReader reader = new FileReader(file)) {
// Logic to read file content
return "File content";
} catch (IOException e) {
throw new MyException("Read error: " + e.getMessage());
}
}
}
Using the FileManager:
public class Main {
public static void main(String[] args) {
FileManager manager = new FileManager();
try {
String content = manager.readFile("path/file.txt");
System.out.println(content);
} catch (MyException e) {
System.err.println("Error: " + e.getMessage());
}
}
}
# What is the Finally Block?
The finally block is a section that always executes after the try and catch blocks, regardless of whether an exception was thrown or not.
It is the ideal place to put cleanup code, such as closing files or releasing resources, ensuring that these operations are performed no matter what happens in the try and catch blocks.
Characteristics of the Finally Block
-
Guaranteed Execution: The finally block will always execute, whether or not there is an exception. The only exception to this rule is if there is a System.exit() in the try or catch block.
-
Releasing resources: It is commonly used to close resources like input/output streams or database connections to avoid performance issues.
Example of Finally Block Usage
Let's expand the file management system example to include a finally block that guarantees the FileReader is closed correctly.
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class FileManager {
public String readFile(String path) throws MyException {
File file = new File(path);
FileReader reader = null;
if (!file.exists()) {
throw new MyException("The file does not exist.");
}
try {
reader = new FileReader(file);
// Logic to read file content
return "File content";
} catch (IOException e) {
throw new MyException("Read error:" + e.getMessage());
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// Handling error when trying to close the resource
System.err.println("Error closing: " + e.getMessage());
}
}
}
}
}
In this code, even if an exception occurs while reading the file or if a MyException is thrown, the finally block ensures that the FileReader is closed correctly.
# Conclusions
Effective error handling is fundamental for building reliable and easy-to-maintain applications.
Understanding how exceptions work in Java, how to create your own exceptions, and how to use try-catch blocks to handle unexpected situations will allow you to write more robust and error-resistant code.
Adopting solid error handling practices will not only improve the quality of your applications but will also make them safer and more pleasant for the final user.