So far, we have shown how to handle exceptions when they occur, and how the try/catch/finally
block allows us to control what the code does in the event of an exception.
But, it is also possible to manually throw exceptions in your code and use them inside try/catch/finally
blocks to control possible errors.
The throw
statement
We can throw exceptions in Java using the throw
statement. In order to be able to throw an exception, the Object thrown must be a Throwable
or one of its subclasses. It is also possible to throw custom exceptions.
This is usually done by creating a class that extends the Exception
class. Once an exception is thrown, it can be handled by a try
block. If no suitable try
block is found for the exception to be handled, the execution will stop.
As an example, let’s create a method that divides only positive integers. For the arguments to be valid, the dividend
must be greater or equal than 0 and the divisor
must be greater than 0, as we can’t divide by 0:
public class PositiveIntegerDivider {
int divide(int dividend, int divisor) {
try {
System.out.println("Try block executed");
if (dividend < 0 && divisor <= 0) {
throw new IllegalArgumentException("dividend is not greater or equal than 0 and divisor is not greater than 0");
} else if (dividend < 0) {
throw new IllegalArgumentException("dividend is not greater or equal than 0");
} else if (divisor <= 0) {
throw new IllegalArgumentException("divisor is not greater than 0");
}
return dividend/divisor;
} catch(IllegalArgumentException e) {
System.out.println("Catch block executed");
System.out.println("An IllegalArgumentException occurred: " + e.getMessage());
return -1;
}
}
}
We will call our method in the following way:
PositiveIntegerDivider divider = new PositiveIntegerDivider();
// the divisor and the dividend are both negative, so an IllegalArgumentException is thrown
System.out.println("The result is: " + divider.divide(-50,-10));
In this case, if we call our method with a dividend
of -50 and a divisor
of -10, the output will be:
Try block executed
Catch block executed
An IllegalArgumentException occurred: dividend is not greater or equal than 0 and divisor is not greater than 0
The result is: -1
Furthermore, if we call our method with a dividend
of -50 and a divisor
of 10:
PositiveIntegerDivider divider = new PositiveIntegerDivider();
// the dividend is negative, so an IllegalArgumentException is thrown
System.out.println("The result is: " + divider.divide(-50,10));
Output:
Try block executed
Catch block executed
An IllegalArgumentException occurred: dividend is not greater or equal than 0
The result is: -1
Moreover, if we call our method with a dividend
of 50 and a divisor
of -10:
PositiveIntegerDivider divider = new PositiveIntegerDivider();
// the divisor is negative, so an IllegalArgumentException is thrown
System.out.println("The result is: " + divider.divide(50,-10));
Output:
Try block executed
Catch block executed
An IllegalArgumentException occurred: divisor is not greater than 0
The result is: -1
And finally, we call our method with a dividend
of 50 and a divisor
of 10, so that no exception is thrown:
PositiveIntegerDivider divider = new PositiveIntegerDivider();
// the divisor and the dividend are both positive, so no is thrown
System.out.println("The result is: " + divider.divide(50,10) );
Output:
Try block executed
The result is: 5
Throwing exceptions in this way is useful, as it allows you to throw exceptions with custom messages to give a better indication of what went wrong.
Also, as mentioned above, it lets you throw objects of classes you’ve extended from the Exception
class, to be even more specific about what caused the exception in your application.
Checked and unchecked exceptions
So far, we have always used a try
block when we throw exceptions. But for some exceptions, it is possible to just throw them without handling them. If an exception is not handled, it will stop the execution of the current method and report the exception that caused it.
This is only possible with what are known as Unchecked Exceptions. Many common exceptions belong to this category, such as ArithmeticException
, IllegalArgumentException
, NullPointerException
, among others.
On the other hand, exceptions categorized as Checked Exceptions must be handled with a try
block. This is verified when you compile your program, and will result in an error if the exception is not being handled.
One of the most common exceptions in this category is IOException
, which is thrown when an input or output operation runs into an error (for example when reading data from a file that doesn’t exist).
When creating custom exceptions, it is important to decide what kind of exception you want your type to be. Unchecked exceptions, like ArithmeticException
, IllegalArgumentException
, and NullPointerException
, are derived from RuntimeException
, so your custom exception class should extend from it.
To create a custom checked exception, you should extend the Exception
class. SInce they are checked exceptions, when your custom exception is thrown it will be mandatory for it to be handled.
Throwing checked exceptions: the throws
statement
As mentioned before, if your method throws a checked exception, it must be handled inside a try
block. But, it is not mandatory to do so in the method that the exception is first thrown. This is useful as it might be better to react to the exception where the method was originally called from.
To indicate that a method throws a checked exception that must be handled, the throws
statement is used in the declaration of the method. It also requires us to specify the class of the exception that is being thrown.
For example, if we create a method for reading a single String from a file, we have to indicate that the method throws an IOException
:
String read(String fileAddress) throws IOException {
File inputFile = new File(fileAddress);
BufferedReader reader = new BufferedReader(new FileReader(inputFile));
return reader.readLine();
}
When we call this method somewhere else, we have to either handle the exception with a try
block:
String methodForString(){
StringFromFile reader = new StringFromFile();
String s;
try {
s = reader.read("D:\\input.txt");
} catch (IOException ex) {
System.out.println("An IOException occurred: " + ex.getMessage());
s = null;
}
return s;
}
or we can again include a throws
statement in the declaration of this method and keep propagating the exception up to where the method was called:
String methodForString() throws IOException {
StringFromFile reader = new StringFromFile();
String s = reader.read("D:\\input.txt");
return s;
}
You can also indicate a superclass of the exception thrown to cover more than one type of exception. Additionally, you can specify more than one class after the throws statement (for example throws IOException, NullPointerException ).
|