1. Types of exceptions
All exceptions are divided into 4 types, which are actually classes that inherit one another.
Throwable
class
The base class for all exceptions is the Throwable
class. The Throwable
class contains the code that writes the current call stack (stack trace of the current method) to an array. We'll learn what a stack trace is a little later.
The throw operator can only accept an object that derives from the Throwable
class. And although you can theoretically write code like throw new Throwable();
, nobody usually does this. The main purpose of the Throwable
class is to have a single parent class for all exceptions.
Error
class
The next exception class is the Error
class, which directly inherits the Throwable
class. The Java machine creates objects of the Error
class (and its descendants) when serious problems have occurred. For example, a hardware malfunction, insufficient memory, etc.
Usually, as a programmer, there is nothing you can do in a situation where such an error (the kind for which an Error
should be thrown) has occurred in the program: these errors are too serious. All you can do is notify the user that the program is crashing and/or write all known information about the error to the program log.
Exception
class
The Exception
and RuntimeException
classes are for common errors that happen in the operation of lots of methods. The goal of each thrown exception is to be caught by a catch
block that knows how to properly handle it.
When a method cannot complete its work for some reason, it should immediately notify the calling method by throwing an exception of the appropriate type.
In other words, if a variable is equal to null
, the method will throw a NullPointerException
. If the incorrect arguments were passed to the method, it will throw an InvalidArgumentException
. If the method accidentally divides by zero, it will throw an ArithmeticException
.
RuntimeException
class
RuntimeExceptions
are a subset of Exceptions
. We could even say that RuntimeException
is a lightweight version of ordinary exceptions (Exception
) — fewer requirements and restrictions are imposed on such exceptions
You'll learn the difference between Exception
and RuntimeException
later.
2. Throws
: checked exceptions
All Java exceptions fall into 2 categories: checked and unchecked.
All exceptions that inherit the RuntimeException
or Error
are considered unchecked exceptions. All others are checked exceptions.
Twenty years after checked exceptions were introduced, almost every Java programmer thinks of this as a bug. In popular modern frameworks, 95% of all exceptions are unchecked. The C# language, which almost copied Java exactly, did not add checked exceptions.
What is the main difference between checked and unchecked exceptions?
There are additional requirements imposed on checked exceptions. Roughly speaking, they are these:
Requirement 1
If a method throws a checked exception, it must indicate the type of exception in its signature. That way, every method that calls it is aware that this "meaningful exception" might occur in it.
Indicate checked exceptions after the method parameters after the throws
keyword (don't use the throw
keyword by mistake). It looks something like this:
type method (parameters) throws exception
Example:
checked exception | unchecked exception |
---|---|
|
|
In the example on the right, our code throws an unchecked exception — no additional action is required. In the example on the left, the method throws a checked exception, so the throws
keyword is added to the method signature along with the type of the exception.
If a method expects to throw multiple checked exceptions, all of them must be specified after the throws
keyword, separated by commas. The order is not important. Example:
public void calculate(int n) throws Exception, IOException
{
if (n == 0)
throw new Exception("n is null!");
if (n == 1)
throw new IOException("n is 1");
}
Requirement 2
If you call a method that has checked exceptions in its signature, you cannot ignore the fact that it throws them.
You must either catch all such exceptions by adding catch
blocks for each one, or by adding them to a throws
clause for your method.
It's as if we're saying, "These exceptions are so important that we must catch them. And if we do not know how to handle them, then anyone who might call our method must be notified that such exceptions can occur in it.
Example:
Imagine that we are writing a method to create a world populated by humans. The initial number of people is passed as an argument. So we need to add exceptions if there are too few people.
Creating Earth | Note |
---|---|
|
The method potentially throws two checked exceptions:
|
This method call can be handled in 3 ways:
1. Don't catch any exceptions
This is most often done when the method does not know how to properly handle the situation.
Code | Note |
---|---|
|
The calling method does not catch the exceptions and must inform others about them: it adds them to its own throws clause |
2. Catch some of the exceptions
We handle the errors we can handle. But the ones we don't understand, we throw them up to the calling method. To do this, we need to add their name to the throws clause:
Code | Note |
---|---|
|
The caller catches only one checked exception — LonelyWorldException . The other exception must be added to its signature, indicating it after the throws keyword |
3. Catch all exceptions
If the method does not throw exceptions to the calling method, then the calling method is always confident that everything worked well. And it will be unable to take any action to fix an exceptional situations.
Code | Note |
---|---|
|
All exceptions are caught in this method. The caller will be confident that everything went well. |
3. Catching multiple exceptions
Programmers really hate to duplicate code. They even came up with a corresponding development principle — DRY: Don't Repeat Yourself. But when handling exceptions, there are frequent occasions when a try
block is followed by several catch
blocks with the same code.
Or there could be 3 catch
blocks with the same code and another 2 catch
blocks with other identical code. This is a standard situation when your project handles exceptions responsibly.
Starting with version 7, in the Java language added the ability to specify multiple types of exceptions in a single catch
block. It looks roughly like this:
try
{
// Code where an exception might occur
}
catch (ExceptionType1 | ExceptionType2 | ExceptionType3 name)
{
// Exception handling code
}
You can have as many catch
blocks as you want. However, a single catch
block cannot specify exceptions that inherit one another. In other words, you cannot write catch (Exception
| RuntimeException
e), because the RuntimeException
class inherits Exception
.
GO TO FULL VERSION