< Zurück | Inhalt | Weiter >

3.2.4 Error Handling, Java Style

Errors in Java are handled through exceptions. In some circumstances, the Java runtime will throw an exception, for example, when you reference a null pointer. Methods you write may also throw exceptions. This is quite similar to C++. But Java exceptions are classes. They descend from Object, and you can write your own classes that extend an existing exception. By so doing, you can carry up to the handler any information you would like. But we’re getting ahead of ourselves here. Let’s first describe the basics of exceptions, how to catch them, how to pass them along, and so forth.

In other programming languages a lot of code can be spent checking return codes of function or subroutine calls. If A calls B and B calls C and C calls D, then at each step the return value of the called function should be checked to see if the call succeeded. If not, something should be done about the error—though that “something” is usually just returning the error code to the next level up. So function C checks D’s return value, and if in error, returns an error code for B to check. B in turn looks for an error returned from C and re- turns an error code to A. In a sense, the error checking in B and C is superflu- ous. Its only purpose is to pass the error from its origin in D to the function that has some logic to deal with the error—in our example that’s A.


Java provides the try/catch/throw mechanism for more sophisticated error handling. It avoids a lot of unnecessary checking and passing on of errors. The only parts of a Java program that need to deal with an error are those that know what to do with it.

The throw in Java is really just a nonlocal “goto”—it will branch the exe- cution of your program to a location which can be quite far away from the method where the exception was thrown. But it does so in a very structured and well-defined manner.

In our simple example of A calling B calling C calling D, D implemented as a Java method can throw an exception when it runs into an error. Control will pass to the first enclosing block of code on the call stack that contains a catch for that kind of exception. So A can have code that will catch an excep- tion, and B and C need not have any error handling code at all. Example 3.17 demonstrates the syntax.


image

Example 3.17 A simple try/catch block

try {

for (i = 0; i < max; i++) { someobj.methodB(param1, i);

}

} catch (Exception e) {

// do the error handling here: System.out.println("Error encountered. Try again.");

}

// continues execution here after successful completion

// but also after the catch if an error occurs


image


In the example, if any of the calls to methodB() in the for loop go awry—that is, anywhere inside methodB() or whatever methods it may call an exception is thrown (and assuming those called methods don’t have their own try/catch blocks), then control is passed up to the catch clause in our exam- ple. The for loop is exited unfinished, and execution continues first with the catch clause and then with the statements after the catch.

How does an error get thrown in the first place? One simply creates an

Exception object and then throws the exception (Example 3.18).


image

Example 3.18 Throwing an Exception, step by step

Exception ex = new Exception("Bad News"); throw ex;


image


Since there is little point in keeping the reference to the object for the local method—execution is about to leave the local method—there is no need to declare a local variable to hold the exception. Instead, we can create the excep- tion and throw it all in one step (Example 3.19).



image

Example 3.19 Throwing an Exception, one step

throw new Exception("Bad News");


image


Exception is an object, and as such it can be extended. So we can create our own unique kinds of exceptions to differentiate all sorts of error conditions. Moreover, as objects, exceptions can contain any data that we might want to pass back to the calling methods to provide better diagnosis and recovery.

The try/catch block can catch different kinds of exceptions much like cases in a switch/case statement, though with different syntax (Example 3.20).

Notice that each catch has to declare the type of each exception and provide a local variable to hold a reference to that exception. Then method calls can be made on that exception or references to any of its publicly available data can be made.

Remember how we created an exception (new Exception("message"))? That message can be retrieved from the exception with the toString() method, as shown in that example. The method printStackTrace() is also available to print out the sequence of method calls that led up to the creation of the exception (Example 3.21).

The exception’s stack trace is read top to bottom showing the most recently called module first. Our example shows that the exception occurred (i.e., was constructed) on line 6 of the class named InnerMost, inside a method named doOtherStuff(). The doOtherStuff() method was called from inside the class MidModule—on line 7—in a method named doStuff(). In turn, doStuff() had been called by doSomething(), at line 11 inside


image

Example 3.20 Catching different kinds of exceptions

try {

for (i = 0; i < max; i++) { someobj.methodB(param1, i);

} // next i


} catch (SpecialException sp) { System.out.println(sp.whatWentWrong());


} catch (AlternateException alt) { alt.attemptRepair(param1);


} catch (Exception e) {

// do the error handling here: System.out.println(e.toString()); e.printStackTrace();

}

// continues execution here after any catch


image


image

Example 3.21 Output from printStackTrace()

java.lang.Exception: Error in the fraberstam. at InnerMost.doOtherStuff(InnerMost.java:6) at MidModule.doStuff(MidModule.java:7)

at AnotherClass.doSomething(AnotherClass.java:11) at ExceptExample.main(ExceptExample.java:14)


image


AnotherClass, which itself had been called from line 14 in the

ExceptExample class’ main() method.

We want to mention one more piece of syntax for the try/catch block. Since execution may never get to all of the statements in a try block (the excep- tion may make it jump out to a catch block), there is a need, sometimes, for some statements to be executed regardless of whether all the try code completed successfully. (One example might be the need to close an I/O con- nection.) For this we can add a finally clause after the last catch block. The code in the finally block will be executed (only once) after the try or after the catch—even if the path of execution is about to leave because of throwing an exception (Example 3.22).


image

Example 3.22 Use of a finally clause

try {

for (i = 0; i < max; i++) { someobj.methodB(param1, i);

} // next i


} catch (SpecialException sp) { System.out.println(sp.whatWentWrong());


} catch (AlternateException alt) { alt.attemptRepair(param1); throw alt; // pass it on


} catch (Exception e) {

// do the error handling here: System.out.println(e.toString()); e.printStackTrace();


} finally {

// Continue execution here after any catch

// or after a try with no exceptions.

// It will even execute after the AlternateException

// before the throw takes execution away from here. gone = true;

someobj = null;

}


image


3.2.5 print(), println(), printf()

We’ve already used println() in several examples, and assumed that you can figure out what it’s doing from the way we have used it. Without going whole- hog into an explanation of Java I/O and its various classes, we’d like to say a little more about the three various output methods on a PrintStream object.6 Two of the methods, print() and println(), are almost identical. They differ only in that the latter one appends a newline (hence the ln) at the end of its output, thereby also flushing the output. They expect a String as their only argument, so when you want to output more than one thing, you add the

Strings together, as in:


image

6. The mention of the PrintStream object was meant to be a hint, to tell you that you can find out more about this sort of thing on the Javadoc pages for the PrintStream object.


System.out.println("The answer is "+val);


“But what if val is not a String?” we hear you asking. Don’t worry, the Java compiler is smart enough to know, that when you are adding with a String argument it must convert the other argument to a String, too. So for any Object, it will implicitly call its toString() method. For any primitive type (e.g., int or boolean), the compiler will convert it to a String, too.

The third of the three output methods, printf(), sounds very familiar to C/C++ programmers, but be warned:

• It is only available in Java 5.07 and after.

• It is similar but not identical to the C/C++ version.

Perhaps the most significant enhancement to printf() is its additional syntax for dealing with internationalization. It’s all well and good to translate your Strings to a foreign language, but in doing so you may need to change the word order and thus the order of the arguments to printf(). For example, the French tend to put the adjective after rather than before the noun (as we do in English). We say “the red balloon” and they say “le balloon rouge.” If your program had Strings for adjective and noun, then a printf() like this:


String format = "the %s %s\n"; System.out.printf(format, adjective, noun);


wouldn’t work if you translate just the format String:


String format = "le %s %s\n"; System.out.printf(format, noun, adjective);


You’d like to be able to do the translation without changing the code in your program.8 With the Java version of printf(), there is syntax for specifying which argument corresponds to which format field in the format string. It uses


image

7. Remember, you’ll need the -source 5.0 option on the command line.

8. Java has good support for internationalization, another topic for which we don’t have the time. The ability to translate the strings without otherwise modifying the program is a crucial part to internationalization, and the printf() in Java 5.0 is certainly a help in this regard. In a similar vein, the Eclipse IDE, covered in Chapter 10, includes a feature to take all string constants and convert them to external properties at a stroke, making internationalization much easier to do.


a number followed by a dollar sign as part of the format field. This may be easier to explain by example; our French translation, switching the order in which the arguments are used, would be as follows:


String format = "le %2$s %1$s\n"; System.out.printf(format, noun, adjective);


The format field %2$s says to use the second argument from the argument list—in this case, adjective—as the string that gets formatted here. Similarly, the format field %1$s says to use the first argument. In effect, the arguments get reversed without having to change the call to println(), only by translat- ing the format String. Since such translations are often done in external files, rather than by assignment statements like we did for our example, it means that such external files can be translated without modifying the source to move ar- guments around.

This kind of argument specification can also be used to repeat an argument multiple times in a format string. This can be useful in formatting Date objects, where you use the same argument for each of the different pieces that make up a date—day, month, and so on. Each has its own format, but they can be combined by repeating the same argument for each piece. One format field formats the month, the next format field formats the day, and so on. Again an example may make it easier to see:


import java.util.Date; Date today = new Date();

System.out.printf("%1$tm / %1$td / %1$ty\n", today);


image

The previous statement uses the single argument, today, and formats it in three different ways, first giving the month, then the day of the month, then the year. The t format indicates a date/time format. There are several suffixes for it that specify parts of a date, a few of which are used in the example.9



NOTE

Don’t forget the trailing \n at the end of the format string, if you want the output to be a line by itself.



image

9. There are many more, familiar to C/C++ UNIX/Linux/POSIX programmers who have used the strftime() library call.


The details for all the different format fields can be found in the Javadoc for the java.util.Formatter class, a class that is used by printf() to do its formatting, but one that you can also use by itself (C programmers: think “sprintf”).

In order to implement printf() for Java, the language also had to be ex- tended to allow for method calls with a varying number of arguments. So as of Java 5.0, a method’s argument list can be declared like this:


methodName(Type ... arglist)


This results in a method declaration which takes as its argument an array named arglist of values of type Type. That is, it is much the same as if you declared methodName(Type [] arglist) except that now the compiler will let you call the method with a varying number of arguments and it will load up the arguments into the array before calling the method. One other implication of this is that if you have a declaration like this:


varOut(String ... slist)


then you can’t, in the same class, also have one like this:


varOut(String [] alist)


because the former is just a compiler alias for the latter.



TIP

We recommend that you avoid methods with variable argument list length. You lose the compile-time checking on the number of arguments that you supply (since it can vary). Often the type of the arguments in the list will be Object, the most general type, to allow anything to be passed in. This, too, circumvents type checking of arguments, and can lead to runtime class-cast exceptions and other problems. Methods with variable argument list length are often a lazy approach, but were necessary to make printf() work, and for that we are grateful.

image


3.3 USING (AND MAKING) JAVA APIS


With every class you write, you define a name—the name of the class. But what if someone else has already used that name? Java programming should encour- age reuse of existing code, so how do you keep straight which names are available?

This is a namespace issue—who can use which names. A classic way to

solve a namespace issue is to divide the namespace up into domains. On the Internet, host names are sectioned off into domains, so that I can have a host named Pluto or www and so can lots of others—because each host is qualified by its domain (e.g., myco.com). Thus www.myco.com isn’t confused with www.otherco.com or www.hisgroup.org. Each host is named www, but each is unique because of the qualifying domain.

Java solves the problem in much the same way, but with the names in the reverse order. Think of the “host” as the class name; the “domain” name, used to sort out identical host names, is, in Java parlance, the package name. When you see a name like com.myco.finapp.Account, that can be a Java package com.myco.finapp qualifying a class named Account.

Beyond just keeping the namespace clean, Java packages serve another important function. They let you group together similar classes and interfaces to control access to them. Classes within the same package can access each others’ members and methods even when they are not declared public, provid- ed they are not declared to be private. This level of intimacy, sometimes called package protection, means that you should group classes together that are related, but avoid grouping too many classes together. It’s tempting just to put all your classes for a project into the same package, for example, com.myco.ourproject, but you will provide better safety and perhaps pro- mote better reuse by grouping them into several smaller packages, for example, com.myco.util, com.myco.financial, and com.myco.gui.


3.3.1 The package Statement

So how do you make a Java class part of a package? It’s easy—you just put, as the first (noncomment) line of the file, the package statement, naming the package to which you want this class to belong. So if you want your Account class to be part of the com.myco.financial package, your Java code would look as shown in Example 3.23.

3.3 Using (and Making) Java APIs 83

image


image

Example 3.23 Use of a package statement

package com.myco.financial;


public class Account

{

// ...

}


image


Making a class part of a package is easy. What’s tricky is putting the class file in the right location so that Java can find it.

Think of the current directory as the root of your package tree. Each part of a package name represents a directory from that point on down. So if you have a package named com.myco.financial then you’ll need a directory named com and within that a directory named myco and within that a directory named financial. Inside that financial directory you can put your Account.class file.

When Java runs a class file, it will look in all the directories named in the

CLASSPATH environment variable. Check its current value:


$ echo $CLASSPATH


$


If it’s empty, as in this example, then the only place where it will look for

your classes will be the current directory. That’s a handy default, because it is just what you want when your class file has no package statement in it. With no package statement, your class becomes part of the unnamed package. That’s fine for simple sample programs, but for serious application development you’ll want to use packages.

Let’s assume that you’re in your home directory, /home/joeuser, and beneath that you have a com directory and beneath that a myco directory with two subdirectories financial and util. Then with your classes in those lower level directories, you can run your Java program from the home directory. If you want to run it from any arbitrary directory (e.g., /tmp or

/home/joeuser/alt) then you need to set CLASSPATH so it can find this package tree. Try:


$ export CLASSPATH="/home/joeuser"

$


Now Java knows where to look to find classes of the

com.myco.financial and com.myco.util packages.


3.3.2 The import Statement

Once we have put our classes into packages, we have to use that package’s name when we refer to those classes—unless we use the import statement.

Continuing our example, if we want to declare a reference to an Account object, but Account is now part of com.myco.financial, then we could refer to it with its full name, as in:


com.myco.financial.Account =

new com.myco.financial.Account(user, number);


which admittedly is a lot more cumbersome than just:


Account = new Account(user, number);


To avoid the unnecessarily long names, Java has import statements. They are put at the beginning of the class file, outside the class definition, just after any package statement. In an import statement, you can name a class with its full name, to avoid having to use the full name all the time. So our example becomes:


import com.myco.financial.Account;

// ...

Account = new Account(user, number);


If you have several classes from that package that you want to reference, you can name them all with a “*”, and you can have multiple different import statements, as in:


import java.util.*;

import com.myco.financial.*;

// ...

Account = new Account(user, number);


Here are a few things to remember about import statements. First, they don’t bring in any new code into the class. While their syntax and placement


is reminiscent of the C/C++ include preprocessor directive, their function is not the same. An import statement does not include any new code; it only aids name resolution. Secondly, the “*” can only be used at the end of the package name; it is not a true wildcard in the regular expression sense of the word. Thirdly, every class has what is in effect an implicit import java.lang.* so that you don’t need to put one there. References to String or System or other core language classes can be made without the need for either the import statement or the fully qualified name (except as described in the next paragraph).

If you need to use two different classes that have the same name but come from different packages, you will still need to refer to them by their full names; import can’t help you here. As an example of this, consider the two classes java.util.Date and java.sql.Date (though with any luck you won’t need to refer to both of them within the same class).


3.4 ENCAPSULATION, INHERITANCE, AND POLYMORPHISM


The classic troika of OOP buzzwords is “encapsulation, inheritance, and polymorphism.” How does Java do each of these things?