In any computer program, bad things can happen despite our awesome code.
Bad things can be errors like trying to access an element in array that doesn't exist.
Or dividing a number by zero.
These failure events are called Java exceptions.
An exception in Java is an unexpected event that happens and the program cannot continue
normal behavior until the problem is addressed.
These exception events are something that shouldn't, but in some rare cases, do happen.
When an exception does happen, we say the exception is thrown.
We can also provide code to run if an exception happens.
This is called catching the exception.
When we catch a thrown exception, we're telling Java our catch block code makes every thing
better and handles the problem gracefully.
Catching exceptions gives our program a chance to recover from a problem, and continue normal
execution.
All exceptions are represented with a class that are subtypes of the java.lang.Exception
class.
Exceptions are also subtypes of Throwable.
Throwable is the superclass class for all types of errors and includes the Error class.
The Error class represents something that happens which we can't do anything about.
An example is the JVM is out of memory.
In this video, we're looking at exceptions only.
Not the errors.
There are two types of exceptions.
runtime exceptions and checked exceptions.
runtime exceptions are exceptions that we should have prevented.
We should never use a variable before it's assigned, so we should never get a NullPointerException.
We should iterate over arrays, so we should never get an ArrayIndexOutOfBoundsException.
These are problems we should prevent using solid programming practices.
Java will not force us to provide code to run if a runtime exception is thrown.
Instead, the program will likely crash.
Checked exceptions are different.
Java forces us to deal with these exceptional conditions.
These are unlikely things that could happen in our application, no matter what we do.
An example is FileNotFoundException.
We could write everything perfectly, but someone might delete the file when we're not looking.
Java asks us to provide code to catch this exception when it's thrown.
We'll cover how to catch exceptions soon.
For now, let’s look at exceptions and how we trace them back to the problem.
Let's create an array with three numbers but try to print out four.
This creates the runtime exception java.lang.ArrayIndexOutOfBoundsException.
In this code, we're forcing the error as an example.
We know this isn't how we should iterate over an array.
When Java hits this error, it throws an exception and exits the program.
This is a very simple stack trace.
It states the runtime exception, in this case an ArrayIndexOutOfBoundsException.
It also tells us which index this exception happened, so in this case 3.
The only way we can tell this is from experience and knowing the exception message.
We'll look at other exception messages later in the video.
The stack trace tells us the line in our code where things went bad.
That's where it says "at com.deegeu.constructors.NewClass.main" and the line number is 20.
If we go back to our code we can find line 20.
Reading the stack trace for this case, we can see the exception, the bad value, and
the line number.
That should be enough to pin point the error.
We can make our call stack deeper by moving the error to a method outside the main method.
Now the stack trace is deeper by one.
As we call methods within methods, the stack trace will grow deeper.
For some Java programs, this stack trace can be very deep.
It really depends on where in our program we are.
Not all stack traces are as kind our simple one.
Some look like this.
The "caused by" tells us that the exception we received above was really caused by this
exception.
We want to look at this line of code to solve the problem.
You can have multiple "caused by" sections in a stack trace, but the last one will likely
be your problem.
In this long example, the final exception isn’t even in our code.
What we want to do in this case is look for the last stack trace message that is coming
from our code.
The first line that references our code is likely the problem.
In this case, our problem is actually in this code.
Our next question is how to we handle checked exceptions?
I say "checked exceptions" because we rarely, if ever, try to catch runtime exceptions.
If we're trying to catch a runtime exception, we really need to think about what we're trying
to accomplish.
However, Java does force us to handle all checked exceptions.
In Java, we can either "catch" checked exceptions using something called a "try-catch" block
in code, or specify a requirement that any method calling our method must handle the
exception.
If our method has the possibility of throwing an exception, we can specify the exception
in the method declaration.
We add the keyword "throws" and the exception class.
We only need to do this for checked exceptions.
Runtime exceptions do not need to be declared.
When we add this code, we're telling anyone calling our method they need to catch this
exception.
Exceptions work like this.
The exception is thrown in a method somewhere in our code.
If we don't catch the exception in this method, we declare our method throws the exception
and Java passes the exception up to the calling method.
If there's code to catch the exception here, it will run the handling code.
Otherwise it will keep working it's way up the call stack until it reaches the main method,
or a method that does catch the exception.
If nothing catches the exception, the program crashes and reports the problem.
How do we catch an exception?
We use a try-catch block.
It starts with the keyword try, a code block to try, and then the keyword catch, the exception
type, and the code to handle the exception.
This code says, "try this code block, and if there's an exception of this type catch
it with this code block".
Once the exception is caught, we execute the next line of code after the catch.
The exception type is important.
Our catch block applies only for the exception type we state after the catch keyword.
So if we put FileNotFoundException, it will only apply for FileNotFoundExceptions and
any possible FileNotFountException subclasses.
We can put different catch blocks for a single try block.
This allows us to provide handling code specific to the exception.
The exceptions we handle must be ordered from specific exceptions to general exceptions.
The reason is Java will try to match the exception class from top to bottom.
As soon as Java hits an exception it matches, it stop looking.
So if we put Exception as the first class like this, it doesn't matter if there is a
more specific Exception class later.
Exception is the superclass, so this catch applies to Exception and any subtype - which
is everything.
Java will only run one catch block per exception.
Catching just the Exception type is a bad practice.
The reason is, we're telling Java this code handles this exception type.
If we use the Exception superclass, that means we're saying our code handles any error that
can happen.
There are not many cases where this is true.
It's not Pokemon, we don't want to catch them all.
Usually we'd want to recover from a file not found differently than a an error from parsing.
It's very important we put our application back into a runnable state after catching
exceptions.
When we catch an exception, our catch block is run, and then the very next line of code
is executed.
So if we don't completely recover from what ever problem threw the exception, we're just
going to create more problems.
There are three things we might see in code we're maintaining.
The first is a blank catch block.
This hides the problem, as if it never happened.
It's saying, "try this code, and if it fails, ignore the problem and hope no one notices".
What almost always happens is our application is now in an invalid state, and something
bad will happen again later down the road in a completely different location.
This makes the problem very hard to diagnose and fix.
Don't ever do it.
If you don't know how to handle the exception, specify the requirement for the calling class
to handle it.
The next worse thing is, some developers will say "since I can't have a blank catch block,
I'll add a log message", or worse write a message to the console.
That's the same thing as a blank catch block.
In this case, we are writing a note that no one is likely to see, and then putting the
application in a state where something bad can happen later on.
Again, if you don't know how to handle the exception, specify the requirement for the
calling class to handle it.
Finally, we might see code like this.
This is called catching and throwing a new exception.
The problem with this is we are losing the stack trace.
The exception in our try block is handled, and then we are throwing a new exception with
a stack trace starting from here.
If you want to log a message, and then rethrow an exception, this is how you want to do it.
Just use the keyword throw with the same exception object.
This will preserve the stack trace.
And that's a whirlwind tour of Java exceptions.
In the next video we'll look at diagnosing common exceptions, and how to fix them.
Put any questions you have in the comments below.
Liking the video helps me know what I'm doing right, so if you liked the video... you know
what to do.
And with that, I’ll see you in the next tutorial!