Chapter:Exceptions - Two main types

From Juneday education
Jump to: navigation, search

Meta information about this chapter (short)

Expand using link to the right to see the full content.

Introduction

This chapter introduces the two main types of exceptions, RuntimeException and checked Exception. We discuss what the two types are used for and also about the inheritance hierarchy which decides if an exception class is a RuntimeException or a checked exception.

Purpose

The purpose is for the student to know that there are two main types of exceptions and what differs between them.

Goal

After this chapter, the student shall understand:

  • There are two main types of exceptions; runtime exceptions and checked exceptions
  • That runtime exceptions are exception classes below the RuntimeException class in the inheritance hierarchy
  • That runtime exceptions are signs of programming errors (bugs) which need to be fixed
  • That you are not forced to handle code which might throw a runtime exception differently than other code
  • That checked exceptions are all exception classes that inherit from Exception but not also from RuntimeException
  • That checked exceptions need to be handled
    • Either put a try-catch around the code that might throw a checked exception
    • Or declare that the code with such code also might throw a checked exception

Instructions to the teacher

Common problems

Students often fail to understand the "handle or declare" rule. Only checked exceptions ever need to be declared with a throws clause of a method. A method which throws a checked exception does so for one of two reasons:

  1. It calls a method which in turn is declared to throw such an exception
  2. It explicitly throws a checked exception in its code

Runtime exceptions are signs of code which is buggy. NullPointerException is a sign that either someone has called a method with a reference which is null, and the method tries to dereference the null reference as if it was a reference to an object. Parameters should be checked before used. Or, the method obtains a null reference from another reference and tries to dereference it. References you get from a method call (where the method can return null) should be checked. It's never wrong to check for null in any case, before using a reference.

If a method is not meant to be called with null as an argument, you should document so and throw a NullPointerException if this rule is violated. If the method can't work when sent a null argument, then if someone calls the method with a null argument, that someone creates a bug. That's why the method should throw an exception.

Another thing students struggle with, seems to be the call stack. Spend some time during the lectures on exceptions to talk about the call stack and show how control is transferred from method to method, and how an exception "bubbles back up" through the call stack.

Exceptions: Two main types of Exceptions

Information
Knowing exceptions is fundamental for a Java Programmer. If you want to become a skilled developer, these chapters on exceptions are very important.

First of all, it's important to understand that exceptions in Java are represented by ordinary Java objects. These objects are of some class extending the class Exception (which is a subclass to Throwable). Well, they ordinary objects as they may be, there's one thing about exceptions which make them stand out from other objects, from the Java language perspective. Exception objects can be thrown (typically from a method) and they can be caught (by code calling a method).

The Exception class has one subclass which stands for one family of exceptions called RuntimeException. Runtime exceptions behave just like any other exceptions in that they can be thrown and caught using the language constructs for this. All other exceptions (subclasses to Exception but not in the branch extending RuntimeException) are called checked exceptions.

What earns checked exceptions their name, is that a method which throws such an exception must declare that it does so. The compiler will impose some restrictions on code calling a method which declares that it throws (actually might throw) a checked exception. The compiler checks such code. The rule imposed by the compiler is that code calling a method which may throw a checked exception, must be in a method either declares that it too throws a checked exception, or it must handle the exception which might be thrown. Handling the case that an exception might be thrown is done by catching the exception and providing code which does something about the situation.

The exceptions classes hierarchy

Often the exception classes under RuntimeException, the unchecked exceptions, are exceptions which comes from some kind of programmer mistakes, like trying to divide an integer with zero, trying to use an object via a reference which is null or trying to type case an object to a class which it cannot be cast to.

Since programmers aren't mandated by the compiler to write code to handle such exceptions, when they are thrown from a method, the exception object propagates unhandled to the caller. Since the caller doesn't handle it (doesn't catch it), the exception keeps propagating to the caller of the caller etc until it either reaches a handler or reaches the main method unhandled. If not even the main method has a handler for unchecked exceptions, the program ends abruptly with an error message and the whole chain of calls, all the way from main down to the method where the problem occurred.

Imagine an application whose main method calls method a() which calls method b() which calls method c() and an unchecked exception is thrown from c(). Let's pretend that the mistake in c() was that the programmer for c() tried to use a reference which was null, to call an instance method. This doesn't work, of course, and a NullPointerException will be thrown from c() by the runtime system (the JVM). NullPointerException is a RuntimeException and thus unchecked. If there is no handler anywhere in the chain of method calls which handles also RuntimeExceptions, the exception thrown by c() will propagate all the way up to main and crash the program with something like this, as the error message:

Exception in thread "main" java.lang.NullPointerException
	at Program.c(Program.java:12)
	at Program.b(Program.java:9)
	at Program.a(Program.java:6)
	at Program.main(Program.java:3)

You can follow the call chain if you start at the bottom (this is also called a call stack). Main calls, a, which calls b, which calls c which throws a NullPointerException.

So, are unchecked exceptions bad, then? Wouldn't it have been better if NullPointerException had been checked, so that the programmers would have been forced by the compiler to write a handler? To answer that question, we must think about what went wrong and why, and what could we do about it in a handler?

Looking closer at the code when the program crashed, showed the following version of the method c:

static void c(String s){
  s.toUpperCase();
}

The programmer called s.toUpperCase() on a reference to a String (for some reason). The programmer did so, just assuming that the reference s was a reference to an actual String object and not null. Apparently, though, s was null and the call to toUpperCase() failed with a NullPointerException. Now, what could possibly a handler somewhere have done about this? If the handler were declared in the main method, for instance, it is probable that it couldn't really know what happened and where and what to do about it. It knows, however, that something went wrong. We know now, after looking at the code, that the cause of the problem was that someone didn't make sure the reference s wasn't null before using it as a reference to an actual String object.

In this, somewhat simplified and constructed, example, we saw that the problem was actually a bug which could have been fixed with an if-statement checking whether s was null before using s as a reference to a real String, and taken care of null in the else-clause. So actually, just because no one handled the exception and the program crashed with a stack trace (the call stack printout), we could locate the bug and fix it. We are not so sure, however, the the user running the program would make much sense of the error message printout.

Checked exceptions on the other hand, come with the rule that a method must declare that such an exception might be thrown, and that calling code must either handle it or also declare that it might throw an exception (and pass the problem and requirement of handling it up the chain to its callers). The compiler will make sure that some code somewhere handles (catches) the exception thrown by such a method.

When could we use checked exceptions then? Well, first of all, the Java standard API is full of methods which are declared to throw checked exceptions. If we want to use an object from some class in the API and some method in that object "throws" a checked (or several) exception, then we have no choice but to follow the rules imposed by the compiler.

Sometimes we will actually write our own classes for checked exceptions and use them for our own method declarations. Doing so will work as expected, we will pose the restrictions following the rules of "handle or declare that your method throws" on to users of our methods.

One use case for using checked exceptions is when a method might run into some contingency (eventuality). A contingency could be something which isn't exactly good or common (happens rarely) but isn't really a critical error either. In order to understand how exceptions could be used to signal contingencies, we might first want to know what happens when a method throws an exception.

In a normal program flow, when a method calls another method, the control of the program is temporarily moved to the called method. Only when that method exits (for instance using a return statement), control is returned to the calling method, which moves on to its next statement. But when a method throws an exception, execution immediately leaves the method, even if no return statements have been reached yet.

The only way for a calling method to structurally and orderly manage a situation where a call to a method throws an exception, is to have a handler. Otherwise, control and execution will leave also the calling method and the exception will "bubble up" the call chain (be propagated to the caller of this method).

If we go back to contingencies hand how we can use exceptions to signal that something unusual has happened, we can actually view the throwing of an exception as an alternative return path. The normal return statements can then be used to exit the method nicely with some value representing information back to the caller. But if something extraordinarily happens, we can use an exception instead. If it is a checked exception, we can be sure that this will be handled sooner or later, and thus the exception can serve as information bearer of the contingency.

For completeness, we'll mention here that many (most) of the checked exceptions thrown by methods in the Java API actually represent more severe events than merely contingencies. Some checked exceptions thrown by methods in the API include events such as a file missing, a file system error, or a Java class not being found (but needed to be loaded). These are pretty serious situations (depending on the context of course), but the authors of the API decided to make them checked exceptions anyway.

It is possible to have opinions on the choice of making serious problems as checked exceptions as it forces us to write handlers wherever we use these methods and even though there is often not much the program can do to recover from the case of e.g. a broken file system. But we (the authors) are of the opinion that it was indeed a good choice. Making even severe problems checked exceptions, forces us as programmers to prepare for the event that something bad like that actually can (and sometimes do) happen. It makes us write code which is prepared for the worst, so to speak, and at least we can handle it in the eyes of the user of our software in that we can catch the problem and formulate a more understandable error message to the user, than the normal stacktrace printout they would otherwise see.

If we really dislike the decision to make something really bad a checked exception, we could actually use a strategy to make things easier for our colleagues using our methods, if our methods must call API-methods which throw checked exceptions. The strategy is to let our method provide handlers for the checked exceptions from the API calls. In this way users of our methods aren't obliged to write handlers (as they would have been if we just declared that we throw checked exceptions too, instead of handling them).

But wouldn't information get lost about the potentially really bad thing which lead to the exception our method caught? Yes it would. So the solution to this problem, according to the strategy, is that our handlers actually wrap the checked exception inside a runtime exception we throw from the handler! Runtime exceptions thrown by us don't come with the requirement for our callers to handle-or-declare. This allows our callers to proceed and call us as if everything should work fine, but in the event of something bad happening, a runtime exception will get thrown at them. Since they don't handle runtime exceptions, it will propagate up the call chain. This allows our project to put an exception handler somewhere, which actually also handle runtime exceptions, and make the decision of what to do about it. It it is really bad, the global (or general) exception handler can make the decision to terminate the application in a controlled way.

Chapters on the topic of Exceptions

Here are the chapters on the topic of Exceptions, so that you can check your progress:

Videos

English videos

Swedish videos

Links

Source code

  • Code from presentations and solutions (github)

Further reading

Where to go next

The next page has exercises for this chapter: Exceptions_-_Two_main_types_-_Exercises

« PreviousBook TOCNext »