Exceptions Oversimplified


This chapter will begin with an explanation of why exceptions are important. Then it will look at an extended example that uses exceptions. Please bear in mind that the example code here is intended to demonstrate concepts, not to stand as an example of good programming. Later on, once you understand how exceptions affect program flow, you'll see more realistic code examples.

The Trouble with Error Codes

First, let's look at why Java needs a feature to support unusual program status. Imagine a remote database that stores daily rainfall reports for various weather stations. (A remote database is one where the data is stored on a different computer from the one you are using. The two machines are connected by a network.) Without going into the details of how to get data from a remote database into a Java program, imagine a class with a method that somehow retrieves rainfall numbers. The method might be called

float getRainfall(int station, int year,                      int month, int day)

For example, to get the rainfall for station #7 for July 8, 2001, you would call

getRainfall(7, 2001, 7, 8);

You can imagine the method doing network connection stuff, database login/password stuff, database query stuff, network disconnection stuff, and finally returning a value. The problem is, what happens if something goes wrong with the database? Here are a few things that could go wrong:

  • The database computer could be turned off.

  • The network cables could break.

  • The database could be deleted.

  • The database management code could crash.

  • The database password could be changed.

All of these possibilities are beyond the control of the programmer who is writing the getRainfall() method. They are not bugs. A bug is when you write code that doesn't do what you want it to do. Bugs can be avoided by intelligent design and programming, but no amount of programming forethought on your part can prevent someone from walking up to a remote computer and turning it off.

Your code has no straightforward way to deal with these unusual circumstances. There is no straightforward way to tell the method's caller that the database computer was turned off or the password didn't work. Before the invention of exceptions (which predate Java), the only reasonable option was to designate certain special return values, called error codes, to indicate that something unusual happened.

In this example, you might reserve large return values as error codes. You might decide that 10,000 means the password didn't work, 20,000 means the network was unresponsive, and so on. After all, if it ever rains 10,000 inches in a single day anywhere on Earth, we'll all have more pressing problems than data processing to worry about.

What is wrong with this approach? The problem is that anyone, anywhere, who calls getRainfall() has to remember to deal with all the error codes:

float rainfall = getRainfall(7, 2001, 7, 8); if (rainfall < 1000) {   // Process normally. } else if (rainfall == 1000) {   // Deal with password problem. } else if (rainfall == 20000) {   // Deal with unresponsive net. } 

The error-processing code might display a message, or it might be more sophisticated. The password-handling code might try a different password. The network-handling code might retry the query at one-second intervals, or it might page a system administrator. But no matter how the errors are handled, anyone who calls the method has to check all return values to see if an error code was returned. To do this, they need good documentation that describes each code and its meaning. The programming language can do nothing to support error handling, because from the compiler's point of view, an error code is just an ordinary value returned by a method.

Special problems are introduced when the method is revised, if the new revision introduces new error codes. Now all the old documentation is incomplete. It's even worse if the new rev of the method changes the significance of an existing code.

Things can get worse yet. What if you realize that your error codes actually represent legitimate return values? Certainly, there's nowhere on Earth where it rains 10,000 inches in a day. But on other planets, with active atmospheres and extremely long days (Mercury, for example), 10,000 is common.

Less imaginatively, the data might be gathered automatically into the database by electronic rain gauges. If the electronics fail, a gauge could erroneously report a measurement of 10,000. Then, when getRainfall() returned the value, the error-handling code might page a system administrator, who would be paid overtime for rushing to the office at 4:30 in the morning. The administrator would spend hours determining that the network was healthy, and would probably be grouchy for the rest of the day.

You have probably realized that rainfall can never be less than zero, so you should have reserved negative error codes, rather than large ones. That would make your method interplanetary, but the other problems would remain. Still, unless you use exceptions, error codes are the only option. By the time you finish this chapter, you should be an enthusiastic user of exceptions.

Throwing Exceptions

In this section, you will meet two new Java keywords: throw and throws. They look almost identical, but throw only appears in executable code, while throws only appears in method declarations. You will also meet the Exception class. By now, you are aware that Java uses a number of classes that are provided for you by the system. The Object class is an obvious example, and you have also seen a little bit of the String class. In Chapter 12, "The Core Java Packages and Classes," you will learn about more of these classes. They are too numerous to describe in detail, but you will also learn where to find out about provided classes as needed. But in order to learn that, you first have to understand exceptions.

To see exceptions in action, let's change the getRainfall() example. For now, let's suppose that only one unusual condition is recognized by the code: a crashed database. To detect this condition, assume you have a boolean method called databaseOk() that returns true if the database is healthy and false if it has crashed. If you were to use the error-code approach, you might write the following:

1. float getRainfall(int station, int year, 2.                   int month, int day) 3. { 4.   if (databaseOk() == false) 5.     return -1; 6. 7.   // Get & return rainfall from db 8. }

You use -1 as an error code to indicate a crashed database. Now here is the same method, rewritten to use an exception:

 1. float getRainfall(int station, int year,  2.                   int month, int day) throws Exception  3. {  4.   if (databaseOk() == false)  5.   {  6.     Exception x = new Exception("The db crashed.");  7.     throw x;  8.   }  9.   // Get & return rainfall from db 10. }

This changes the code in 3 ways:

  • It adds throws Exception to the declaration on line 1.

  • It creates an instance of the Exception class on line 6.

  • It throws the exception (whatever that means) on line 7.

The addition of throws Exception to the declaration announces that this method now might throw an exception. Any particular call to the method might or might not throw, but any code that calls the method must be prepared to deal with the possibility.

Line 6 is just an ordinary constructor call. Until they are thrown, exceptions are just ordinary objects. There are two commonly used versions of the Exception constructor: a no-args version, and the version used here, which takes a string of text as an argument. The text can be retrieved later by calling the exception's getMessage() method. Later on, you will see how this is useful when processing exceptions.

Line 7 is the big idea. The throw keyword must be followed by an exception. (Strictly speaking, a few other things can follow throw, but they are beyond the scope of this book.) When the throw statement is executed, the current execution of the current method is abandoned immediately. Execution jumps (as if through hyperspace!) to the appropriate exception-handling code for the particular exception that was thrown. The exception handler uses the catch keyword, which is presented in the next section.

Catching Exceptions

It stands to reason that things that are thrown ought to be caught. This is true for balls, Frisbees, kisses, and exceptions.

When you call a method that declares that it throws an exception, the calling code can't just call the method. For example, the following code will not work:

float rainfall = getRainfall(7, 2001, 7, 8);

This call used to be fine, but now getRainfall() throws an exception, which any calling code must be prepared to catch. The call has to look something like this:

 1. try  2. {  3.   float rainfall = getRainfall(7, 2001, 7, 8);  4.   System.out.println("rainfall was " + rainfall");  5. }  6. catch (Exception x)  7. {  8.   System.out.println("getRainfall() failed.");  9.   System.out.println("Message is: " + x.getMessage(); 10. } 11. System.out.println("And life goes on.");

The code on lines 2-5 (in the curly brackets immediately after try) is called a try block. There are two rules to know about try blocks:

  • Any code that throws an exception must appear in a try block. (For now. Later you'll learn how to get around this rule.)

  • At least one statement in the try block must throw an exception.

The code on lines 7-10 (in the curly brackets immediately after the catch line) is a catch block. When a statement in a try block is executed and causes an exception to be thrown, the current pass through the try block is abandoned immediately. Execution jumps to the first line of the catch block.

Note the code in parentheses on line 6, after catch. It looks like a variable declaration, and indeed it is. When an exception is thrown, the JVM makes the exception object accessible to the catch block. When you declare Exception x, you are saying that you want to use the variable name x as the name of your reference to the exception. This variable has scope (that is, valid meaning) only within the catch block.

Notice line 9, which makes a method call on x. Recall that you can pass a message into the Exception constructor. The message is stored in the exception object, and the getMessage() call retrieves it. Recall from the previous section that getRainfall() stored a message that said, "The db crashed."

Note

If you are at all uncomfortable with the way line 9 adds literal text to a method call, please be patient. All will be explained in the next chapter. For now, just be aware that it works.

What happens if the try block runs in its entirety, with no exception being thrown? In this case, the catch block is ignored. Execution jumps around the catch block, from line 4 to line 11. If this is the case, and if the rainfall value is 1.45 inches, the code will produce the following output:

Rainfall was 1.45 And life goes on.

On the other hand, if the call to getRainfall() throws an exception, the output will be

getRainfall() failed. Message is: The db crashed.

The Simple Exception Lab animated illustration demonstrates the flow of execution through code, which is almost identical to this example. To run the program, type java exceptions.SimpleExceptionLab. You will see the display shown in Figure 11.1.

click to expand
Figure 11.1: Simple Exception Lab

You will see the code for a method that calls another method to retrieve a rainfall measurement from an imaginary remote database. (There isn't really a remote database, and your computer doesn't have to be connected to a network for the animated illustration to work.) You can use the checkbox to control whether the imaginary database is working or not. As Figure 11.1 shows, the DB (database) is initially okay. If you uncheck the checkbox, the second method will throw an exception that will be caught by the first method. The text area to the left of the display will show all output from the println statements. Try running the program once with the DB is Ok checkbox checked, to simulate normal execution. Figure 11.2 shows the final state.

click to expand
Figure 11.2: Simple Exception Lab: final state with normal execution

Think about what the code would print out if the database wasn't okay. Run it again with the checkbox unchecked to observe the error-handling behavior. Was the output what you expected?




Ground-Up Java
Ground-Up Java
ISBN: 0782141900
EAN: 2147483647
Year: 2005
Pages: 157
Authors: Philip Heller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net