Exceptions

Although you are just learning about them now, you have probably become well-acquainted with exceptions during the last 17 previous hours. These errors turn up when you write a Java program that compiles successfully but encounters a problem when it runs. For example, a common coding mistake is to refer to an element of an array that doesn't exist, as in the following statements:

String[] greek = { "Alpha", "Beta", "Gamma" };
System.out.println(greek[3]);


In this example, the String array has three elements. Because the first element in an array is numbered 0 rather than 1, the first element is greek[0], the second is greek[1] and the third is greek[2]. The statement attempting to display the contents of greek[3] is erroneous, because that element does not exist. The preceding statements will compile successfully, but when you run the program, the Java interpreter will halt with a message such as the following:

Exception in thread "main" java.lang.ArrayIndexOutBoundsException
 at SampleProgram.main(SampleProgram.java:4)


This message indicates that the app has generated an exception, which the interpreter noted by displaying an error message and stopping the program. The error message includes a reference to a class called ArrayIndexOutOfBoundsException in the java.lang package. This class is an exception, an object that is created to make note of an exceptional circumstance that has taken place in a Java program. When a Java class encounters an exception, it alerts users of the class to the error. In this example, the user of the class is the Java interpreter.

By the way

Two terms are used to describe this process: throw and catch. Classes and methods can throw exceptions to alert others that they have occurred. These exceptions can be caught by other classes, methods, or the Java interpreter.


All exceptions are subclasses of Exception, another class in the java.lang package. The ArrayIndexOutOfBoundsException does what you would expect—it lets you know you have used an array element outside the array's boundaries. There are hundreds of exceptions in Java. Many of them indicate a problem that can be fixed with a change in your program, such as the array exception. These are comparable to compiler errors—once you correct the situation, you don't have to concern yourself with the exception any longer. Other exceptions must be dealt with every time a program runs by using five new statements: try, catch, finally, throw, and throws.

Catching Exceptions in a try-catch Block

Up to this point, you have dealt with exceptions by fixing the problem that caused them. There are times you either can't deal with an exception in that manner or want to handle them within a Java class. As an introduction to why this is useful, enter the short Java app in Listing 18.1 and save it as SumNumbers.java.

Listing 18.1. The Full Text of SumNumbers.java
1: public class SumNumbers {
2: public static void main(String[] arguments) {
3: float sum = 0;
4: for (int i = 0; i < arguments.length; i++) {
5: sum = sum + Float.parseFloat(arguments[i]);
6: }
7: System.out.println("Those numbers add up to " + sum);
8: }
9: }


You can compile this app successfully. The SumNumbers app takes one or more numbers as command-line arguments, adds them up, and displays the total. Because all command-line arguments are represented by strings in a Java app, the program must convert them into floating-point numbers before adding them together. The Float.parseFloat() class method in Line 5 takes care of this, adding the converted number to a variable named sum. Run the app with the following command (or use another Java tool where you can specify the same seven numeric arguments):

java SumNumbers 8 6 7 5 3 0 9


The app will display the following output:

Those numbers add up to 38.0


Run the program several times with different numbers as arguments. It should handle all of them successfully, which might make you wonder what this has to do with exceptions. To see the relevance, run the SumNumbers app with the following command:

java SumNumbers 1 3 5x


The third argument in this example contains a typo—there shouldn't be an x after the number 5. The SumNumbers app has no way to know this is a mistake, so it tries to add 5x to the other numbers, causing the following exception to be displayed:

Exception in thread "main" java.lang.NumberFormatException: 5x
 at java.lang.FloatingDecimal.readJavaFormatString(Unknown source)
 at java.lang.Float.parseFloat(Unknown source)
 at SumNumbers.main(SumNumbers:5)


This message is informative to the programmer developing the app, but it's not something you would want a user to see. Java programs can take care of their own exceptions by using a TRy-catch block. This block statement takes the following form:

try {
 // statements that might cause the exception
} catch (Exception e) {
 // what to do when the exception occurs
}


A TRy-catch block should be used with any exception that you want a program to handle. The Exception object that appears in the catch statement should be one of two things:

  • The class of the exception that might occur
  • A superclass of several different kinds of exceptions that might occur

The TRy section of the TRy-catch block should contain the statement or statements that might throw an exception. In the SumNumbers app, the call to the Float.parseFloat() method in Line 5 will throw a NumberFormatException whenever it is used to convert a non-numeric string to a floating-point value. To improve the SumNumbers app so that it never stops running with this kind of error, you can use a try-catch block that deals with this exception. Open your text editor and create an app called NewSumNumbers.java from Listing 18.2.

Listing 18.2. The Full Text of NewSumNumbers.java
 1: public class NewSumNumbers {
 2: public static void main(String[] arguments) {
 3: float sum = 0;
 4: for (int i = 0; i < arguments.length; i++) {
 5: try {
 6: sum = sum + Float.parseFloat(arguments[i]);
 7: } catch (NumberFormatException e) {
 8: System.out.println(arguments[i] + " is not a number.");
 9: }
10: }
11: System.out.println("Those numbers add up to " + sum);
12: }
13: }


After you save and compile the app, run it with a non-numeric command-line argument along with a few numbers, such as the following:

java NewSumNumbers 1 3 5x


Running it with these arguments produces the following output:

5x is not a number Those numbers add up to 4.0


The try-catch block in Lines 5–9 deals with NumberFormatException errors that are thrown by the Float.parseFloat() method. These exceptions are caught within the NewSumNumbers class, which displays an error message for any argument that is not a number. Because the exception is handled within the class, the Java interpreter does not display an error message when something like 5x is used as a command-line argument. You can often deal with problems related to user input and other unexpected data by using try-catch blocks.

Watch Out!

You also can use TRy and catch to deal with errors that could be fixed by making some changes to your program, such as the ArrayIndexOutOfBoundsException problem that was described earlier this hour. This isn't recommended—the easiest solution is to change your program so the error never occurs.


Catching Several Different Exceptions

TRy-catch blocks can be used to handle several different kinds of exceptions, even if they are thrown by different statements. Listing 18.3 contains an app called DivideNumbers that takes two integer arguments from the command-line and uses them in a division expression. This app must be able to deal with two potential problems in user input:

  • Non-numeric arguments
  • Division by zero
Listing 18.3. The Full Text of DivideNumbers.java
 1: public class DivideNumbers {
 2: public static void main(String[] arguments) {
 3: if (arguments.length == 2) {
 4: int result = 0;
 5: try {
 6: result = Integer.parseInt(arguments[0]) /
 7: Integer.parseInt(arguments[1]);
 8: System.out.println(arguments[0] + " divided by " +
 9: arguments[1] + " equals " + result);
10: } catch (NumberFormatException e) {
11: System.out.println("Both arguments must be numbers.");
12: } catch (ArithmeticException e) {
13: System.out.println("You cannot divide by zero.");
14: }
15: }
16: }
17: }


Compile the app and try it out with some integers, floating-point numbers, and non-numeric arguments. The if statement in Line 3 checks to make sure that two arguments were sent to the app. If not, the program exits without displaying anything. The DivideNumbers app performs integer division, so the result will be an integer. This isn't going to be as accurate as division using floating-point numbers—in integer division, 5 divided by 2 equals 2, not 2.5. If you use a floating-point or non-numeric argument, a NumberFormatException will be thrown by Lines 6–7 and caught by Lines 10–11. If you use an integer as the first argument and a zero as the second argument, a ArithmeticExpression will be thrown in Lines 6–7 and caught by Lines 12–13.

Handling Something After an Exception

When you are dealing with multiple exceptions by using try and catch, there are times when you want the program to do something at the end of the block whether an exception occurred or not. You can handle this by using a try-catch-finally block, which takes the following form:

try {
 // statements that might cause the exception
} catch (Exception e) {
 // what to do when the exception occurs
} finally {
 // statements to execute no matter what
}


The statement or statements within the finally section of the block will be executed after everything else in the block, even if an exception occurs. One place this is useful is in a program that reads data from a file on disk, which you will do in Hour 20, "Reading and Writing Files." There are several ways an exception can occur when you are accessing data—the file might not exist, a disk error could occur, and so on. If the statements to read the disk are in a try section and errors are handled in a catch section, you can close the file in the finally section. This makes sure that the file will be closed whether or not an exception was thrown as it was read.

Throwing Exceptions

When you call a method of another class, that class can control how the method is used by throwing exceptions. As you make use of the classes in the Java class library, the compiler will often display a message such as the following:

NetReader.java:14: unreported exception java.net.MalformedURLException; must be caught or declared to be thrown


Whenever you see an error stating that an exception "must be caught or declared to be thrown," it indicates that the method you are trying to use throws an exception. Any class that calls these methods, such as an app that you write, must do one of the following things:

  • Handle the exception with a TRy-catch block.
  • Throw the exception.
  • Handle the exception with a TRy-catch block and then throw it.

Up to this point in the hour, you have seen how to handle exceptions. If you would like to throw an exception after handling it, you can use a tHRow statement followed by the exception object to throw. The following statements handle a NumberFormatException error in a catch block and then throw the exception:

try {
 principal = Float.parseFloat(loanText) * 1.1F;
} catch (NumberFormatException e) {
 System.out.println(arguments[i] + " is not a number.");
 throw e;
}


When you throw an exception in this manner, it generally means that you have not done everything that needs to be done to take care of the exception. An example of where this might be useful: Consider a hypothetical program called CreditCardChecker, an app that verifies credit card purchases. This app uses a class called CheckDatabase, which has the following job:

  1. Make a connection to the credit card lender's computer.
  2. Ask that computer whether the customer's credit card number is valid.
  3. Ask the computer whether the customer has enough credit to make the purchase.

As the CheckDatabase class is doing its job, what happens if the credit card lender's computer doesn't answer the phone at all? This kind of error is exactly the kind of thing that the TRy-catch block was designed for, and it is used within CheckDatabase to handle connection errors. If the CheckDatabase class handles this error by itself, the CreditCardChecker app won't know that the exception took place at all. This isn't a good idea—the app should know when a connection cannot be made so it can report this to the person using the app. One way to notify the CreditCardChecker app is for CheckDatabase to catch the exception in a catch block, and then throw it again with a throw statement. The exception will be thrown in CheckDatabase, which must then deal with it like any other exception. Exception handling is a way that classes can communicate with each other in the event of an error or other unusual circumstance.

Ignoring Exceptions

The last technique that will be covered today is the laziest: how to ignore an exception completely. A method in a class can ignore exceptions by using a throws clause as part of the method definition. The following method throws a MalformedURLException, an error that can occur when you are working with World Wide Web addresses in a Java program:

public method loadURL(String address) throws MalformedURLException {
 URL page = new URL(address);
 loadWebPage(page);
}


The second statement in this example, URL page = new URL(address); creates a URL object, which represents an address on the Web. The constructor method of the URL class throws a MalformedURLException to indicate that an invalid address was used, so no object can be constructed. The following statement would cause one of these exceptions to be thrown:

URL source = new URL("http:www.java24hours.com");


The string http:www.java24hours.com is not a valid URL. It's missing some punctuation—two slash characters (//) after the colon. Because the loadURL() method has been declared to throw MalformedURLException errors, it does not have to deal with them inside the method. The responsibility for handling this exception falls to any class that calls the loadURL() method.

      
Comments