17.7 Exception Handling and the Exception Hierarchy in Java
Exception handling is a fundamental aspect in Java programming, as it allows a program to react appropriately to unexpected conditions during execution. Exceptions in Java are objects that represent abnormal situations that may occur during the execution of a program. By understanding the hierarchy of exceptions and learning how to handle them correctly, programmers can create more robust and reliable applications.
Exception Hierarchy in Java
In Java, all exceptions are descendants of the Throwable
class. This class is divided into two main subclasses: Error
and Exception
. The Error
class is used for serious error situations that the application should not normally attempt to handle, such as Java virtual machine (JVM) problems. The Exception
class is the one we are interested in for handling exceptions, as it includes all the exceptions that an application may want to handle.
Exceptions of the Exception
class are divided into two categories: checked exceptions and unchecked exceptions. Checked exceptions are those that the compiler requires to be handled or declared in the method signature. Examples include IOException
and SQLException
. Unchecked ones, a subclass of RuntimeException
, do not need to be explicitly treated or declared. Common examples are NullPointerException
and IndexOutOfBoundsException
.
Methods and the throws
Clause
When a method can throw an exception that it does not handle, it must declare this possibility using the throws
clause in its signature. This is mandatory for checked exceptions, but optional for unchecked exceptions. The throws
clause informs the method caller that it must be prepared to handle this exception or, alternatively, propagate it.
public void myMethod() throws IOException {
// Code that can throw an IOException
}
If the myMethod
method above throws a IOException
, it does not need to handle this exception internally; instead, it propagates it to the method that called it. This calling method then needs to handle the exception or also declare it in its signature with the throws
clause.
Handling Exceptions with try-catch
To handle exceptions, we use the try
and catch
blocks. The try
block contains the code that can throw an exception, while the catch
block contains the code that is executed if an exception occurs. We can have multiple catch
blocks to handle different types of exceptions.
try {
// Code that can throw exceptions
} catch (IOException e) {
// Code to handle IOException
} catch (SQLException e) {
// Code to handle SQLException
}
It is important to note that catch
blocks must follow the order of specificity, from the most specific to the most generic, because once an exception is caught by a catch
, subsequent blocks are not evaluated.
finally clause
There is also the finally
clause, which is optional and follows the try-catch
blocks. The code inside the finally
block runs regardless of whether an exception is thrown or not, making it useful for executing housekeeping code such as closing connections or freeing resources.
try {
// Code that can throw exceptions
} catch (Exception e) {
// Code to handle exceptions
} finally {
// Code always executed after try or catch
}
Exception Propagation
Sometimes it may be desirable or necessary to propagate an exception so that it can be handled elsewhere. This is done by simply not catching the exception in a catch
block or throwing it again inside a catch
block using the throw
keyword.
public void myMethod() throws IOException {
try {
// Code that can throw an IOException
} catch (IOException e) {
// You can do something with the exception and throw it again
throw e;
}
}
Custom Exceptions
Developers can also create their own custom exceptions by extending the Exception
(for checked exceptions) or RuntimeException
(for unchecked exceptions) class. This is useful when you want to create exceptions specific to your application's use cases.
public class MinhaExcecao extends Exception {
public MinhaExcecao(String message) {
super(message);
}
}
In summary, exception handling in Java is a powerful tool for creating robust and reliable programs. By understanding the exception hierarchy and knowing how to declare, throw, and handle exceptions, you can ensure that your program handles unexpected situations in a controlled manner.