Exception
Let’s kick off this game of Exceptions by understanding what an Exception is. Does it always signify something negative?
An exception isn’t necessarily indicative of an error; rather, it signifies a deviation from the normal flow of execution. Such deviations may occur when the application is improperly coded, as in the case of a NullPointerException.
Similarly, situations may arise where the application is unable to proceed, such as being unable to store a document due to insufficient disk space or inaccessible network locations. These scenarios are typically represented as exceptions within the application, prompting the need for alternative actions when such exceptions occur.
Usually, there’s a way to handle exceptions. We can instruct the application on how to respond if a specific type of exception occurs. That’s why we use exceptions to represent various scenarios in the business flow. However, errors are situations we always strive to avoid. They are instances where the application is unable to take any action and can only shut down, such as an OutOfMemoryError.
Below is the image of a typical exception hierarchy in java.
Types of Exception
- Exceptions are classified into two types
- Checked Exception
- Unchecked Exception (Runtime Exception)
Checked Exception is that type of situation when you throw you have to treat it. If you don’t, your application will not compile.
Unchecked Exceptions are called as Runtime Exception. Because the class representing them is RuntimeException and its child classes. Those are the exceptions that can get into runtime and quit the thread in which it is happening. It’s not checked by the compiler.
We can’t throw the checked exception directly without telling the program how to deal with it. (Compile-time error).
But RuntimeException and its child classes are acting as unchecked. So we don’t need to tell the compiler how to deal with it.
We won’t directly use the Exception or RuntimeException classes, as these are parent classes utilized by Java. Instead, we’ll create our own specific classes to represent the exceptions we might encounter. For example:
public class MyCheckedException extends Exception {
public MyCheckedException() {
super("This is my exception message!");
}
}
public class MyRuntimeException extends RuntimeException {
public MyRuntimeException() {
super("This is my runtime exception!");
}
}
Then, we can throw the exception by using throw keyword
public class Main {
public static void main(String[] args) {
throw new MyRuntimeException();
}
}
Generally, we don’t see exceptions thrown like this. They should be part of the logic in your application. Let’s create another runtime exception.
public class TooMuchMoneyWithdrawnException extends RuntimeException {
public TooMuchMoneyWithdrawnException() {
super();
}
}
If it is a RuntimeException, we will not get any compile-time error; we can directly throw the exception.
public class AccountController {
private static final double MAX_AMOUNT = 100;
public void withdraw(double amount) {
if(amount > MAX_AMOUNT) {
throw new TooMuchMoneyWithdrawnException("Insufficient Amount");
}
// do something
}
}
But if it is a checked exception, then we need to handle it.
public class TooMuchMoneyWithdrawnException extends Exception {
public TooMuchMoneyWithdrawnException(String message) {
super(message);
}
}
Otherwise, it will no longer compile.
Either we have to handle it inside withdraw() or handle it inside the caller of withdraw(). To handle it inside the caller of this method we have to throw it using throws keyword.
public class AccountController {
private static final double MAX_AMOUNT = 100;
public void withdraw(double amount) throws TooMuchMoneyWithdrawnException {
if(amount > MAX_AMOUNT) {
throw new TooMuchMoneyWithdrawnException("Insufficient Amount");
}
// do something
}
}
To handle it, we typically use try-catch-finally blocks. Alternatively, we can use throws inside main() as well to
propagate it from the main() method, but this is not recommended. In this case, it is basically equivalent to
converting a checked exception to a runtime exception.
Exception in Inheritance
While overriding, we can override a method without throwing any exception or with the exact exception. However, we cannot override with a more general type, i.e., a parent type of that exception. Any type of method, whether it throws an exception or not, can be overridden with a runtime exception.
For example, I have created two exceptions one is checked exception (MyCheckedException) another one is runtime exception (TooMuchMoneyWithdrawnException).
- Now let’s see below scenarios
- We can override without throwing any exception.
public class A { public void m() throws MyCheckedException, TooMuchMoneyException { } } public class B extends A { @Override public void m() { } }
- Can override with one of the thrown exceptions also.
public class A { public void m() throws MyCheckedException, TooMuchMoneyException { } } public class B extends A { @Override public void m() throws MyCheckedException { } }
- Can override with both.
public class A { public void m() throws MyCheckedException, TooMuchMoneyException { } } public class B extends A { @Override public void m() throws MyCheckedException, TooMuchMoneyException { } }
- Can override with any RuntimeException.
public class A { public void m() throws MyCheckedException, TooMuchMoneyException { } } public class B extends A { @Override public void m() throws RuntimeException { } }
- Can not override with more general type.
public class A { public void m() throws MyCheckedException, TooMuchMoneyException { } } public class B extends A { @Override public void m() throws Exception { // Compiletime Exception } }
- Can override with any RuntimeException.
public class A { public void m() throws MyCheckedException, TooMuchMoneyException { } } public class B extends A { @Override public void m() throws NullPointerException { } }
- We can override without throwing any exception.
We can throw checked exceptions from an Interface as well. Then, the class providing the implementation should handle it.
Why do we add checked exceptions in an interface?
It’s to clearly define in the contract that this method may sometimes throw this kind of exception. Now, any object consuming that behavior described by the contract is obligated to handle that exception. Typically, contracts decouple the objects using the functionality from those implementing the functionality. However, to ensure that the object using the functionality is aware that it must handle an exception, we define it in the contract itself.
All of the above cases just work the same in case of interface too.
Sometimes you throw Runtime exceptions for documentation purposes.
public interface I1 {
void m() throws MyRuntimeException;
// sometimes you throw runtime exception for document purposes
}
In our next segment, we’ll explore how to deal with exceptions effectively. Stay tuned for practical tips to tackle these unexpected bumps in the road!