When bad things happen to good programs.
In the normal course of execution, methods call other methods, which call other methods.
Normally those methods return and the stack unwinds with any returned values being returned back up the stack from called methods to their callers.
Sometimes things go wrong and a method can’t do what it’s supposed to do.
A file you need to open doesn’t exist.
You need to make a network connection but the network is down.
Remember invariants?
The things that must be true in order for a method to work.
The things that must be true after the method has been called.
(Unless the precondition wasn’t satisfied. Then all bets are off.)
Anything that has to be always true about instances of the class.
With the slight caveat that while a method is running, the class invariants may be briefly violated.
But they must be restored before the method returns.
And you need to be careful about calling other methods while any invariant is perturbed.
Calls go down, return values come back up.
Suppose the quux
method can’t do what it’s supposed to do so can’t return the value it is supposed to compute.
Which means baz
, who called it, is also outta luck.
private int quux() throws MyException {
if (cantDoTheThing()) {
throw new MyException("Something bad happened");
}
return computeValue();
}
As it stands, this will crash your program (or thread).
private int bar() {
try {
var x = baz();
return frob(x);
} catch (MyException me) {
return BAR_DEFAULT_VALUE;
}
}
We’re assuming baz
has no way to deal with the exception and therefore also couldn’t return the value we needed but at this level we have a way to recover, such as by returning a default value.
bar
catches the exception, stopping it from propagating.
Then bar
returns normally and foo
is none the wiser.
throw
throw new SomeException("A message");
This is how we throw an new exception.
Typically the thing after throw
is an expression constructing a new exception object but it can be anything that evaluates to an expression.
try/catch
try {
// Code that might throw an exception
} catch (SomeException e) {
// Code to deal with the exception
}
try/catch/finally
try {
// Code that might throw an exception
} catch (SomeException e) {
// Code to deal with the exception
} finally {
// Code that runs regardless of whether the try block
// threw or completed normally. Even runs if the code
// in the try block contains a return statement.
}
Can also use try/finally
with no catch
to make sure some cleanup code runs.
try (FileReader fr = new FileReader(path);
BufferedReader br = new BufferedReader(fr))
{
// code that uses the resources declared above
}
Makes sure the resources are properly closed after the code in the try block runs, regardless of whether any exceptions were thrown, either in the block or when trying to create the resources.
Throwable
The root of the hierarchy of things that can be used with throw
and catch
.
You will almost never refer to Throwable
directly. I.e. you won’t construct a Throwable
, catch Throwable
, or subclass Throwable
directly.
Throwable
holds a special place in my heart because it’s the one place I left a mark on the language as I convinced them to add the getCause()
method.
Error
Errors are for indicating things that have gone badly enough that the program probably cannot be expected to recover.
AssertionError
This is the error to throw when an invariant is violated.
An invariant being violated is due to a bug in the program.
A caller failed to establish a precondition
A method failed to establish its postcondition despite its precondition being satisfied
An object got into a state that violates the class invariant.
VirtualMachineError
“Thrown to indicate that the Java Virtual Machine is broken or has run out of resources necessary for it to continue operating.”
Subclasses:
OutOfMemoryError
StackOverflowError
InternalError
UnknownError
Exception
Exceptions are typically used to indicate that something out of the program's control has prevented it from doing what it’s supposed to do.
Classes descended from Exception
that do not descend from RuntimeException
are called “checked exceptions” and must be declared to be thrown by any method that can throw them directly or indirectly.
public void frob(Thing t) throws FrobnicationException {
// whatever
}
The throws
clause declares that it’s possible that this method will throw a FrobnicationException
.
Callers of this method must either declare that they also throw it or must catch it.
public void throwIt() throws FrobnicationException {
frob(someThing);
}
This method calls frob
and has no way to recover if frob
fails so it has to let it propagate.
public void catchIt() {
try {
frob(someThing);
} catch (FrobnicationException fe) {
// recover somehow
}
}
On the other hand, if it’s possible to recover, you should.
Exceptions are part of a class’s API and therefore should be at the same level of abstraction.
Don’t leak implementation details by letting low-level exceptions propagate out of your methods.
public void highLevelMethod() throws HighLevelException {
try {
someLowLevelThing();
} catch (LowLevelException lle) {
throw new HighLevelException(lle);
}
}
Best of both worlds.
Exception thrown is appropriate to the abstraction.
But for debugging purposes, it carries with it the underlying cause.
This is my feature!
RuntimeException
Exceptions that descend from RuntimeException
are called “runtime” or “unchecked” exceptions.
Unlike checked exceptions they don’t need to be declared in a throws
clause and typically aren’t.
They are mostly used for things that can go wrong in so many places that almost all code would have to declare them if they were checked.
ArithmeticException |
int division by zero |
IndexOutOfBoundsException |
Accessing a collection with an invalid index. |
ArrayIndexOutOfBoundsException |
Accessing an array with an invalid index. |
StringIndexOutOfBoundsException |
Accessing a String with an invalid index |
NullPointerException |
Trying to invoke a method or access a field on null . |
Possibly.
Kotlin, an “improved” Java treats all exceptions as unchecked.
The main issue is that there are usually a bunch of layers between were the exceptions is thrown and where it can reasonably be handled and it’s not clear that annotating all those layers with throws
clauses helps much.