TIL: Exceptions
Today we’re going to talk about exceptions and why they are useful.
I remember back about a year ago when I first started to seriously work on some python projects. Before then I really hadn’t ever worried too much about handling errors. If there was a problem, it was something I did, and I would just need to fix it. But when I started working on things I wanted other people to be using, I needed to take a different approach.
I’m pretty sure I wrote a blog post about this before, about why exceptions were such an improvement over the way I had been doing things with lots of if statements and hard coded response codes. I just didn’t know any better.
Going over exceptions again now with Java, they seem even more useful than before. In fact, I couldn’t imagine trying to implement the kind of stuff I was doing before. They make both you and your users' lives much easier by doing things that way.
Going to try to keep myself timeboxed at hit the highlights of what new things I learned.
What Are Exceptions?
Exceptions are essentially errors that are hit while running your code. You expected a certain piece of code to behave one way… except it didn’t. And because there was no other path for it to take (like checking conditionals), out popped this exception.
The exception contains a bunch of information with it. Things like a type which helps to describe what kind of issue it was, an error message which will include even more detail about the issue, and even a full stack trace to help you understand what exactly went wrong and where.
This point about stack traces is especially useful to understand as exceptions are going to be passed back up your stack. You might have some logic that requires 4 or 5 method call chains, and an error is found down on the final level. That error will be thrown all the way back up the stack to the start, with the stack trace showing you what went wrong and where.
Exceptions extend the Throwable class, which extends from the Object class. On the same level as Exceptions are Errors, but these are things that are related more to the JRE than things in our code.
To check for exceptions in your code, you’re typically going to be implementing a try/catch/final
block of code. The thing you want to try to do will be placed in the try block, the exception will be caught and handled (typically thrown) in the catch block, and then any cleanup that needs to be done can be handled in the catch block.
Let’s take a look at two really basic examples.
Example: Handling Exception
Let’s write a little bit of code that could cause us to result in an exception being thrown. I’m going to create a super basic class called MathContainer
which is going to have a single method called div
which will divide one int by a second, and return an int:
|
|
Right away you should start to see the issue with this. If I pass in a 0 value into b, we’re going to try to divide by 0. And what happens when we try to do that? We get an exception thrown: java.lang.ArithmeticException: / by zero
.
In this case, the ArithmeticException
that’s thrown is what would be considered an unchecked exception
(more on that below). Because this is going to handle our exception generation for us. There’s really nothing else we NEED to do here. However, we could clean it up a little bit if we wanted to be even more explicit:
|
|
Where we really need to make our changes to is when we’re calling this method. Now we need to be putting our call into a try/catch block to ensure that we’re handling the exception and not blowing up our program.
|
|
Notice how I’m only catching the general Exception
and not the specific ArithmeticException
that is getting thrown. Java will do its best to match against the exception that’s closest to what was thrown. This means that you can actually have MULTIPLE catch statements that can be hit. Lets modify our test method so that it will return back three different levels of Exceptions for us:
|
|
Now we’ll tweak the test files a bit so that they’ll hit our new logic and handle the different exceptions:
|
|
When we run these tests, we actually end up with output that looks like the following, proving how each level of Exception was hit depending on its type returned:
|
|
Checked vs Unchecked Exceptions
There are two main types of Exceptions in Java: checked and unchecked exceptions.
Unchecked exceptions are exceptions that extend the RuntimeException
class. These exceptions technically do not need to be looked for. The compiler will not complain or spit up warnings if you’re not checking for them. This is what we saw with the code above. We actually didn’t need to put anything in here and it was able to handle throwing the exception for us.
The other case are checked exceptions. These are pretty much everything else under the base Exception
class that aren’t RuntimeException
s. This would be things like IOExeption
s you might see when trying to work with files. In this case, you must explicitly handle these exceptions. In fact, your compiler is going to yell at you and refuse to build your code until you do.
When you are explicitly defining that your method may be throwing an exception, you’re denoting this by adding on a throws <Exception>
clause to the definition of the method. When this exists, any code that tries to call or interact with this method MUST be done within a try/catch
block. This throws tells the end user that hey, something might go wrong here, and you need to be ready to deal with it.
Conclusion
There’s a lot more to talk about regarding exceptions, but I feel like these are the main points and enough to get going with. You can create your own custom exceptions if you feel like you really need them, but it’s recommended to usually just use what’s available to you unless it just doesn’t fit your case. When defining your own Exceptions, it’s really as easy as creating a new class that extends from Exception, and then creating a few basic methods within them.
Hopefully this was useful to someone. As always, the code from this post is available in my github repo.
💚 A.B.L.