Chapter:Exceptions - Code examples

From Juneday education
Jump to: navigation, search

Meta information about this chapter

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

Purpose

The students must see some code in order to learn and understand about exceptions. This chapter shows how to declare that a method throws an exception, how to throw one and also how to catch an exception.

Goals

The students should after this chapter (and exercises) know

  • How to declare that a method throws an exception
    • That it concerns checked exceptions
  • How to throw an exception
  • How to catch an exception

Instructions to the teacher

Common problems related to this chapter

Listening to lectures and reading code is all well, but make sure that the students do the exercises for this chapter. And really try to encourage them to ask for help if they get stuck or don't understand how this works.

Exceptions: Examples (with code) using exceptions

This section deals with the syntax and code to use when dealing with exceptions. We'll show you the syntax for declaring that a method throws an exception, how to throw an exception and how to catch an exception in a handler. We'll also look at what an exception object is, how to create one and what methods an exception has for the handler who caught it.

Let's start with checked exceptions, because calling such a method will according to the rules force us to either declare that we too are in a method which throws an exception, or to write a handler catching the exception.

Stuff which typically throw checked exceptions in the API classes, is stuff which has to do with I/O (input/output) like opening files, reading from or writing to files. In fact, there's a whole family of checked exceptions dedicated to the problems which may occur with I/O, IOException and its subclasses.

If we want to open a file for writing, the API provides quite a few classes for us to do so. We'll look at two of them. If we want to write text to a file, we need some kind of object which is a writer with some convenient methods for writing such as println(). The PrintWriter class seems just right for us. So, in order to create a PrintWriter for a file, we must construct the PrintWriter providing an object which can write to a file. We'll use FileWriter to represent that object. This book doesn't cover file management techniques, so please be aware that this is just an example for you, to see how to deal with IOExceptions. The constructor for PrintWriter can take a FileWriter as argument, so we'll look at the constructor for FileWriter next. In the API documentation, we find this constructor for FileWriter which seems just right for our example:

public FileWriter(String fileName) throws IOException

That seems simple enough, just provide the file name, and we'll get an object which knows how to write to that file. It will fit perfectly for the constructor of our PrintWriter! What we decide to do, is this:

PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));

But there's this thing about the constructor declaration above. Did you notice the throws IOException part? It tells us that in order to call this constructor, we'd have to handle checked exceptions, in particular some kind of IOException. That shows us the syntax for declaring that a constructor or method throws some exception! Just add a throws clause to the declaration and specify the families of exceptions which may be thrown.

Ok, so back to our example. We need to create a PrintWriter, so we needed to create a FileWriter for its constructor. But the constructor says that it might throw some exception which we must handle somehow. Why is it doing this to us? Come, on, what could possibly go wrong when opening a file for writing? Actually quite a lot might go wrong! What if the String provided for the file name, is actually the name of a directory? That wouldn't be particularly easy to write to. And what if the file doesn't exist, so the FileWriter tries to create it, but the file system and operating system says that we are not allowed to do that for some reason? Or what if the file indeed does exist, but the file system and operating system doesn't allow us to write to it, because it is write protected or owned by some other user etc?

So we'd better be thankful for the heads up and decide how to handle such events. We have now learned the rules, and we don't want to argue with the compiler (it won't be convinced by us anyway), so we know what to do. Either we declare that our method where we want to open the file, also throws at least an IOException. Or, we have to handle the possible IOException which might be thrown by the constructor of FileWriter.

So let's look at the basic syntax for writing a handler. The technique is to put stuff which might throw an exception we'll handle in a block called a try-block:

try{
  stuffWhichMightThrowException();
}

If an exception occurs anywhere in the try block, execution will immediately jump to the end of the try-block, where we'll have our handler. The handler is written as a block called a catch-block (at least we call it that now):

try{
  stuffWhichMightThrowException();
}catch(ExceptionType e){
  // code that deals with the exception known in our handler as "e"
}

As you see, the catch-block starts with a pair of parentheses which declare a parameter of some exception type (which should match the exception we try to catch) and a parameter name for this exception, here e. As with any parameters, you can name it whatever you want, but we stuck with e for brevity. Inside the block of the handler, we can now use the exception object referred to by e in order to further investigate what has happened.

So, now it is time to talk a little about what an exception object (like the one we caught) is and what we can do with it! Like any object, it has a toString() method, which can be used for a textual representation of the exception, for instance if we want to log what happened to some error logfile. Additionally, exceptions have a few more methods, two of which we'll describe next. The getMessage() method, returns a String which may have a message for us. Perhaps the thrower created the exception, passing a message along to the constructor of the exception. If so, we can get the message by calling this method. Another interesting method which exceptions have, is the printStackTrace() method. This is a void method, which uses the standard error stream (in Java represented by System.err) to print the full stacktrace which lead up to the exception. This is also very useful for logging the root cause of the problem. Again, we'd like to remind you that stacktraces probably is better targeted towards programmers trying to understand and fix the problem, than towards the users of the program. It's good then, that it prints to standard error, which can be redirected and hidden from the user.

OK, so now, we have at least a few methods we could use on the exception we catch. We could then complete our handler for the construction of our PrintWriter to at least catch any exceptions and turn them into something more user friendly, as an error message. Note that this doesn't fix the problem at all, it just stops the exception from propagating up, potentially all the way to main, which might lack a handler too, which would make the program crash with a stacktrace!

static void writeFile(){
  try{
    PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));
  }catch(IOException ex){
    System.err.println("Couldn't open file for writing: " +
                        ex.getMessage());
  }
}

On a side note, please notice that the try-block and catch-block both are written using indentation, consistent with the indentation for the method itself. We should use indentation wherever we are using blocks.

What would the output of this error handler look like, if an exception was thrown and caught?

$ javac WritingFile.java && java WritingFile
Couldn't open file for writing: OutFile.txt (Permission denied)

In the example output above, we see in action one of the things which might go wrong when trying to open a file for writing. In this case, we were not allowed to open the file. Perhaps it was owned by some other user.

What would a stacktrace have looked like? An example stacktrace for the same problem (if left unhandled) could have looked something like this:

java.io.FileNotFoundException: OutFile.txt (Permission denied)
	at java.io.FileOutputStream.open(Native Method)
	at java.io.FileOutputStream.<init>(FileOutputStream.java:221)
	at java.io.FileOutputStream.<init>(FileOutputStream.java:110)
	at java.io.FileWriter.<init>(FileWriter.java:63)
	at WritingFile.writeFile(WritingFile.java:11)
	at WritingFile.main(WritingFile.java:6)

What would the syntax for our example method have been if we decided instead not to handle the IOException ourselves? According to the rules, we were allowed to declare that our method too throws an IOException, instead of handling it. We would use the same syntax as the constructor for FileWriter, actually!

static void writeFile() throws IOException{
  PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));
}

So the syntax in this case goes throws IOException. What would the consequence of this decision be for the caller of our method? Let's for simplicity say that it is main which calls our method (as shown in the stacktrace ;-) ). According to the rules, it is now main which has the decision to either handle it or declare that it too throws an exception. Now, declaring that the main method throws an exception isn't very productive, because there is no one who cares and handles it. Declaring that main throws exception is sort of careless behavior, and on par with having a handler which only re-throws the exact same exception. So, we decide here that the author of main knows how to behave, and chooses to write a handler. The handler in main can use the same syntax as our handler did in the example above.

Now, the consequence of our decision to declare that our method throws IOException put some restrictions on the author of the main method. Whether this is a good thing or bad thing is up for debate. It is important that you understand, however, that these decisions on who to put the responsibility for handling exceptions, will be a topic for the project programmers meetings. Some will argue that the method which causes the exception should deal with it, so that they don't have to write handlers all over the place, just to call your method which should work most of the time just fine. Others will argue that it is a good thing that every caller is aware of the problems your method might run into. And, to make stuff worse for people trying to learn Java, others still will argue that there is a middle ground, a compromise we can use here.

This leads us back to the strategy we discussed in the previous section, where we caught an exception and re-threw it as some other exception (more precisely, re-threw it as an unchecked exception).

A very simple example of this middle ground goes something like this. First, the main method (or some method very close to main) should have a handler for all bad things that can happen but aren't handled elsewhere. This includes (at least some) runtime exceptions. Anything which might be thrown somewhere in the system could then be handled in a central place, where the appropriate measures can be taken. If it is convenient to let code call our method without the "burden" of writing handlers, our method could be written without a throws clause. But what about the IOException for the permission denied error, then? It must be handled by our method. Those are the rules. What our method could do then, is to catch the IOException and in the handler, re-throw it as some kind of runtime exception (which will only be caught by the central exception handler which could be in main, for instance).

Now, the people who'd complain about having to handle IOExceptions from your method would be pleased, since they don't have to do anything. And the people who are particular about knowing about problems your method might run into are pleased, because they get their handler in the main, and the handler there can be written to catch also the runtime exception you are throwing when you catch an IOException.

The following, oversimplified of course, example shows how your method could be re-written to catch the IOException and wrap it inside a RuntimeException together with a message:

static void writeFile(){
  try{
    PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));
  }catch(IOException ex){
    throw new RuntimeException("Problem writing: "+ex.getMessage(), ex);
  }
}

The global handler in main (also very simplified, of course) could look something like this:

public static void main(String[] args){
  try{
    writeFile();
  }catch(Exception e){
    System.err.println("Something went wrong: " + e.getMessage());
    System.exit(1);
  }
}

The error message, if the global handler in main was put to work would then look like the following:

$ javac WritingFile.java && java WritingFile
Something went wrong: Problem writing: OutFile.txt (Permission denied)

Note that you now also got the basic syntax for throwing an exception! The keyword used was throw and the thing you throw is a reference to an exception object. The code in the example, threw a brand new RuntimeException, created on the fly.

Is it reasonable to throw runtime exceptions, since we do not get the help from the compiler to warn people that they might be thrown? Yes, of course it is. Why, of course? Because we don't keep secrets from the users of our objects and classes. If a public method might throw a runtime exception by the code we write, then we document that and make sure everyone who will call our method knows about this. Then it is up to them to ignore it or make sure there a handler further up the call chain.

An example of when it makes perfect sense to throw a runtime exception (and document it) is if we have a rule for some class, which says that some parameter is not allowed to be a null reference. Let's say that we have a constructor for a class Member (to create objects representing members of some association for a member system) which takes two parameters, name and email (both as String references). The rule says that the name parameter can never be null, or a NullPointerException will immediately be thrown by the constructor. We do this to prevent the system from ever creating a Member object whose name is null. The email, on the other hand, is not so important. Perhaps not every member has an email address even, so we allow our objects to have a null email.

The reasoning here, is that it is better that the system detects an attempt to create a Member with a null name and treat it as a programmer error, even if it leads to a program crash (or global handler terminating the program or so), then to hope that no code will do this by mistake.

The constructor could then look something like the below, if we accept that name and email are also instance variables of type String in the class:

public Member(String name, String email){
  // Name is NOT allowed to be null!!!
  if(name == null){
    throw new NullPointerException("Name cannot be null");
  }
  this.name  = name;
  this.email = email; // we can live with null emails ;-)
}

Of course it might seem a little drastic to throw a runtime exception from a constructor, but the fact is that this is not so uncommon at all. The only thing we have to keep in mind, is that this must of course be documented (and probably communicated to the programmers if they tend to forget stuff or read the documentation sparsely).

A quick search in the API source code for openjdk-7 shows that a NullPointerException is thrown at 550 places in the java top level package classes (classes in a package which starts with "java.").

If you want an alternative to the if-statement which is checking for null, there is a nice alternative in the Objects class, in the requireNonNull(T) method. It has two versions, one which takes an argument and throws a NullPointerException if it is null, and one that takes an argument and a String message and throws a NullPointerException if the first argument is null, and it sets the message passed as argument as the message of the NullPointerException. It can be used like this:

public Member(String name, String email){
  Objects.requireNonNull(name, "Name cannot be null");
  this.name  = name;
  this.email = email;
}

Any attempt to create a new Member with null as the parameter for name will result in a NullPointerException which left unhandled would result in a stacktrace similar to this:

Exception in thread "main" java.lang.NullPointerException: Name cannot be null
	at java.util.Objects.requireNonNull(Objects.java:228)
	at Member.<init>(NonNullExample.java:12)
	at NonNullExample.main(NonNullExample.java:5)

Finally in this section, we'd like to give you some advise. First, avoid to write a handler just be cause you "have to" and do nothing in the catch-block. This may sound self-explanatory, but it is sadly quite common practice not only among examples in literature and courses (often for brevity) but also among real programmers in the wild. It may solve a compiler error, but it will probably not handle the exception if you opt to do nothing when catching it. Second, try to avoid too broad Exception types in the catch-block. If you were to catch(Throwable t) you would catch every possible type of problem, regardless of whether it is an Error, Exception, RuntimeException or checked exception. It is probably not likely that you have a solution which fits every class of exception. So try to be precise and name as specific type of exception to catch as possible. It is legal to have several catch-blocks after each other, each catching a different family of exceptions. That seems like a good idea, if your try-block may set off many types of exceptions.

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_-_Code_examples_-_Exercises

« PreviousBook TOCNext »