Download Java Exceptions

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts
no text concepts found
Transcript
Java Exceptions
Bob Dickerson
January 2005
1 What is an exception in a program?
1.1 Exceptions
An exception in a program is an “exceptional event” which means an unexpected or unusual outcome of
the execution of some statement. For example, in Java, an exception will be caused if an attempt is made
to follow a pointer that doesn’t point to anything, in other words trying to call a method through a variable
that doesn’t hold an object. Here is an example:
class Except011 {
public static void main(String args[]) {
String s=null;
int len;
len = s.length();
System.out.println("length of: "+s+" = "+len);
}
}
The program is in the file Except011.java. Notice that on line 5 an attempt is made to find the length of
a string object when there is no object, the variable “s” is null. If this program is compiled and executed it
produces the following error:
Exception in thread "main" java.lang.NullPointerException
at Except011.main(Except01.java:5)
What happens is that as the program is executed the Java virtual machine monitors execution of statements
and if something “exceptional” occurs that will prevent a statement producing a reasonable, “unexceptional”
result then it will throw an exception. If the exception is not caught then it will cause the program to
terminate.
1.2 Handling exceptions
What really matters in programming languages is not just that the exception occurs but that the language
provides a way for the programmer to handle the exception, which means take some action if an exception
occurs. In general the feature of the language to deal with exceptions is called an exception handler, the
way it is incorporated in a language varies, but most are very similar, here we will only describe how it is
done in Java.
Any statements that are going to produce an exception that needs to be handled are surrounded by a try
catch statement. The structure of try is:
try {
code that can throw exception
} catch ( exception-type variable ) {
code to deal with exception
}
Notes:
• the ordinary statements to be executed occur between the try and catch
• each catch clause has a pattern (that’s the “exception-type variable” bit) that matches an exception
type, followed by a statement block to executed if the exception occurs and is matched,
1
2 EXCEPTIONS THAT ARE NOT CAUGHT
2
• there can be more than one catch clause each with a different pattern, this can be used to catch
different exceptions,
• if an exceptional event occurs execution of the ordinary statements stops and the system makes an
exception object of a specific exception type (all exceptions in Java are sub-types of Exception),
• once an exception object is made the catch clause is tested, if the exception object type matches the
pattern type then the following statement block is executed,
• the catch statements (the handler) can be used either to attempt to recover of terminate gracefully, see
the later section 6.
Here is an example of adding a handler to the previous program:
class Except02 {
public static void main(String args[]) {
String s=null;
int len=0;
try {
len = s.length();
} catch(Exception e) {
System.err.println("Caught: "+ e);
System.exit(1);
}
System.out.println("length of: "+s+" = "+len);
}
}
The program is in the file Except02.java. Notice that:
• here only one statement is contained in the try, the one that gets the length of s,
• the catch clause has the pattern “Exception e” which means it will match any exception, the actual
exception object is assigned to e,
• when the program is run it will still fail because there is still no object in variable s, and exception
object will be created and “thrown”,
• then the handler statements are executed, in this case they just print a message and terminate the
program. The message is the word “Caught:” joined to the string value of e which holds the caught
exception. This is the output if the program is executed:
Caught: java.lang.NullPointerException
• it never gets to the last print statement.
2 Exceptions that are not caught
In Java and its standard library there are about 350 different exceptions and these are “thrown” by thousands
of different methods (functions). Nearly all of them must be dealt with explicitly by the programmer.
Dealing with an exception means it must be:
• caught with a try...catch statement, or
• “passed back”, in other words, the function within which the exception occurs must pass it back to the
statement that called that function where it must either be caught or passed back. . . . A function passes
back an exception by including the words “throws exception-type” immediately after its argument
list, eg:
void copy(InputFileStream f) throws Exception {
...
Here is an example of a very simple program that opens a file and counts the number of characters in it by
reading them one by one:
2 EXCEPTIONS THAT ARE NOT CAUGHT
3
import java.io.*;
class Except03 {
public static void main(String args[]) {
FileInputStream inf;
int ch, n=0;
inf=new FileInputStream(args[0]);
ch=inf.read();
while(ch!=-1) {
n++;
ch=inf.read();
}
System.out.println("chars read=" + n);
}
}
The program is in the file Except03.java. Unfortunately this program will not even compile correctly,
there at least two sorts of exception that can be thrown, and the program does nothing to deal with them and
this is a compilation error in Java. Here are the error messages:
freckles(541)$ javac Except03.java
Except03.java:6: unreported exception java.io.FileNotFoundException;
must be caught or declared to be thrown
inf=new FileInputStream(args[0]);
^
Except03.java:7: unreported exception java.io.IOException;
must be caught or declared to be thrown
ch=inf.read();
^
the first exception that might occur is when the file is opened and, for example, doesn’t exist. The next
place is in the function read where any I/O error can raise an exception.
Instead of catching these exceptions they can be passed back, since the exceptions occur in the main we
must alter the first line of main:
class Except04 {
public static void main(String args[]) throws Exception {
FileInputStream inf;
...
Now it isn’t necessary to use a try statement. However the function is main so there is no other code to
pass it back to, in this case Java will use its default exception handler which stops the program and prints a
message identifying the exception and giving a back trace of the functions called.
2.1 Exceptions that don’t need catching or passing back
The previous section said that it is a compiler error not to catch an exception or “pass it back”, however
the very first example of an exception, Except011.java, that produced a NullPointerException, didn’t
do either. Why didn’t Except010.java give a compiler error when Except03.java did? Because there are
about 10 exceptions produced by the Java virtual machine not by library routines that are treated specially.
They are RuntimeExceptions, a subset of the type Exception, and NullPointerException is one of
them. These exceptions do not require either a try handler or a throws Exception “pass back” (although
you can if you wish).
Another such special exception is IndexOutOfBoundsException (you should be able to guess from
the name what causes it!). Here is a simple example:
class Except010 {
public static void main(String args[]) {
int a[] = new int[10];
a[11] = 5;
}
}
There is no try and no throws.. in the header of main yet it still compiles, yet if it is run:
Exception in "main" java.lang.ArrayIndexOutOfBoundsException: 11
at Except010.main(Except010.java:4)
3 CATCHING MORE THAN ONE TYPE OF EXCEPTION
4
3 Catching more than one type of exception
This section will consider how a try...catch statement can catch different exceptions from the same
block of statements. The example will be the previous program that read a file and counted the characters,
this version has a try in:
import java.io.*;
class Except05 {
public static void main(String args[]) {
FileInputStream inf;
int ch, n=0;
try {
inf=new FileInputStream(args[0]);
ch=inf.read();
while(ch!=-1) {
n++;
ch=inf.read();
}
} catch (FileNotFoundException e) {
System.err.println("Error opening: "+args[0]);
System.exit(1);
} catch (IOException e) {
System.err.println("IO error exception");
System.exit(1);
}
System.out.println("chars read=" + n);
}
}
The program is in the file Except05.java. As noted before this program can give two sorts of exception:
FileNotFoundException and IOException. The try...catch statement can have any number of catch
clauses at the end, each has an exception pattern and a block of statements to execute if the thrown exception
object matches the pattern.
The order of matching exceptions is important. If the order is reversed and it checks for IOException
first then it will produce a compiler error:
Except06.java:16: exception java.io.FileNotFoundException
has already been caught
} catch (FileNotFoundException e) {
^
1 error
this is because IOException is more general than FileNotFoundException and will match it aswell. All
the exceptions are part of an inheritance hierarchy, see figure 1. Any “higher” class will match all its
Exception
IOException
ClassNotFoundException
lots more
RuntimeException
SocketException FileNotFoundException IndexOutOfBoundsException
NullPointerException
ArrayIndexOutOfBoundsException
Figure 1: Exception inheritance tree
direct or indirect derived classes. So the class name Exception will match any exception. Consequently a
sequence of catch clauses and patterns should always start with more specific ones and the most general
patterns should appear last.
4 More about “passing back” exceptions
If a function doesn’t handle an exception itself, instead it has throws Exception in its heading, where will
the exception be passed back to? It goes back to the point where the function was called and looks for an
4 MORE ABOUT “PASSING BACK” EXCEPTIONS
5
exception handler there. Consider this simple program that opens two files and uses a function to copy one
to the other:
import java.io.*;
public class Except07 {
public static void copy(InputStream in, OutputStream out) throws IOException {
byte buffer[] = new byte[1024];
int rc;
rc = in.read(buffer,0,1024);
while(rc > 0) {
out.write(buffer,0,rc);
rc = in.read(buffer,0,1024);
}
out.close();
}
public static void main(String[] args) throws Exception {
copy(new FileInputStream(args[0]), new FileOutputStream(args[1]));
}
}
The program is in the file Except07.java. Notice that this version doesn’t attempt to handle either the
exception for opening a file or the one from doing I/O. If an IOException occurs in the function copy it
will be “thrown” out of copy back to main where it was called, main in turn will throw it back, and since
there is nowhere else to go the program will terminate.
There are at least three alternative ways of dealing with exceptions in this situation:
1. ignore them, don’t handle them, just throw them back, this is what is done in the program above,
Except07.java,
2. let copy handle the IOException and let main handle the FileNotFoundException, or
3. allow copy to “throw” the IOException and handle it and the FileNotFoundException with a
try..catch clause around the function call statement inside main.
The next program Except08.java uses the last alternative because it illustrates how a caller function can
handle exceptions thrown by the function it calls:
import java.io.*;
public class Except08 {
public static void copy(InputStream in, OutputStream out) throws IOException {
byte buffer[] = new byte[1024];
int rc;
rc = in.read(buffer,0,1024);
while(rc > 0) {
out.write(buffer,0,rc);
rc = in.read(buffer,0,1024);
}
out.close();
}
public static void main(String[] args) {
try {
copy(new FileInputStream(args[0]), new FileOutputStream(args[1]));
} catch (FileNotFoundException e) {
System.err.println("Error opening: "+e.getMessage());
System.exit(1);
} catch (IOException e) {
System.err.println("IO error exception");
System.exit(1);
}
}
}
The program is in the file Except08.java. If, while executing copy an exception occurs, then:
• execution will stop
• an IOException object will be created,
5 THROWING EXCEPTIONS BY PROGRAM
6
• the exception will be thrown back, out of copy to main, to the call statement,
• the call statement is enclosed in a try..catch so the Java runtime system will compare the exception
with the catch patterns,
• it is not a FileNotFoundException so the first clause won’t match, but it is an IOException so the
second clause will match,
• the statements of the matching clause print out an error and stop.
If, on the other hand, an incorrect file name is given as the first argument then
new FileInputStream(args[0])
file fail and the following sequence of events will occur:
• the Java runtime system will create and throw a FileNotFoundException object,
• it will be caught by the try..catch statement,
• the first catch clause will match, and it will execute the following statements,
• the print statement includes a call on:
e.getMessage()
this gets a message out of the exception object e, the message is added when the exception occurs
and the object is created, the message is used to give a simple description of the cause of the failure.
If this program is run and given an incorrect file name the output is:
freckles(624)$ javac Except08.java
freckles(625)$ java Except08 datafile x
Error opening: datafile (No such file or directory)
the string “datafile (No such file or directory)” is returned by getMessage(),
Note that in this program using getMessage() is very important because there are two reasons why
FileNotFoundException can be thrown, either opening the file to read, or opening the file to write, the
message will let the user know which of the two file opens failed.
5 Throwing exceptions by program
Sometimes it is useful or necessary for the programmer to throw an exception under program control.
One situation where this is likely is when somebody writes a library class or component for other people
to use. The developer provides a service, the library class, and the other people are “clients”, they are
the programmers who will use it. When a library class is developed it provides a service to the clients’
requirements, however the developer must set limits to what it can do and under what circumstances it will
do it. What can the developer if these assumptions are broken? The library class will throw an exception.
As an example consider the following (very pathetic) class written by Flaky Software Ltd., it is a simple
stack:
class Stack {
private String stk[];
private int topp;
public Stack(){ stk=new String[100]; topp=-1;}
boolean empty() { return topp<0; }
void push(String s) throws Exception {
if(topp<99)
stk[++topp]=s;
else
throw new Exception("stack full");
}
String top() throws Exception {
if(! empty())
5 THROWING EXCEPTIONS BY PROGRAM
7
return stk[topp];
else
throw new Exception("stack empty");
}
void pop() throws Exception {
if(! empty())
topp--;
else
throw new Exception("stack empty");
}
}
The program is in the file Except111.java. Notice that:
• the storage is allocated as a fixed sized array, therefore if more than 100 items are pushed on to the
stack then it is impossible to carry out the request for the client. What to do? The client is attempting
the impossible and expecting impossible things from Flaky Software products so the developer throws
an exception,
• alternative what happens if the client code tries to remove (pop()) two items off the stack when only
one has been added (pushd(..))? Once again he is making unfair use of our stack so we throw an
exception, there is nothing else we can do without producing a bad result.
How does a program throw an exception?
• firstly the function that needs to produce an exception must include throws Exception in the header,
• a new exception is produced and given a message by, for example:
new Exception("stack full")
• it is then thrown with throw.
As an example now consider what happens if some client code attempts, very unkindly, to push more
than 100 objects onto the stack:
public class Except111 {
public static void main(String args[]) {
Stack lifo = new Stack();
try {
for(int i=0; i<110; i++) lifo.push("str"+i);
while(! lifo.empty()) {
System.out.print((String)lifo.top() + " " );
lifo.pop();
}
System.out.println();
} catch(Exception e) {
System.err.println("Exception: "+e.getMessage());
System.exit(1);
}
}
}
The program is in the file Except111.java. Note that the cautious client realised that he was in danger
of abusing the stack so that he (or she) provided a try..catch clause. Then the program was run and
produced the following output:
freckles(635)$ javac Except111.java
freckles(636)$ java Except111
Exception: stack full
this message was produced by the catch clause in the client code. A similar result, with a different message,
would occur if top orpop were used with an empty stack.
6 THE PHILOSOPHY OF HOW TO USE EXCEPTIONS
8
5.1 Defining your own exception type
In the first version of the Stack class the exception throw was of type Exception, the most general type.
This is not very good; if the client uses the stack with code that can create other exceptions then it will
be hard to select the different types of exception in the catch clauses. It would be better to define a
new exception type. Defining a new exception is quite easy, its just a class that extends Exception and
provides a simple constructor for the message:
class StackException extends Exception {
public StackException(String m) { super(m); }
}
class Stack {
private String stk[];
private int topp;
public Stack(){ stk=new String[100]; topp=-1;}
boolean empty() { return topp<0; }
void push(String s) throws StackException {
if(topp<99)
stk[++topp]=s;
else
throw new StackException("stack full");
}
String top() throws StackException {
if(! empty())
return stk[topp];
else
throw new StackException("stack empty");
}
void pop() throws StackException {
if(! empty())
topp--;
else
throw new StackException("stack empty");
}
}
The program is in the file Except113.java. This version of Stack has its own StackException. Notice that the class only has a constructor that takes a string message, and even that is only passed to the
constructor for the parent class Exception using super(m).
6 The philosophy of how to use exceptions
Historically there has been some debate about how exceptions should be used. Some of the original motivation came from real-time embedded system that had to keep running at all costs or at least shutdown in
a controlled way. Examples of such systems are rocket control systems, hospital systems or railway signalling systems. One of the first well known languages to include exceptions and exception handling was
Ada. Previously programs could crash and there were more ad hoc, varied ways of dealing with problems.
The original intention was that programs would be be fully debugged and tested and exception handling
would only be used for very extreme cases, for things that almost “couldn’t happen”.
However once a mechanism, like try..catch, exists in a programming language programmers are
tempted to use it in other ways. This is probably not a good idea, the exception handling mechanism is not
really suitable as a control structure for ordinary programming. For example since the use of an incorrect
name to open a file throws an exception in Java it might be tempting to catch it, give an error to the user
and try again. There is nothing “exceptional” about a stupid user typing a file name incorrectly, there is a
much better way to deal with this using a File object to check the correctness of a file name before trying
to open it. This allows the problem to be dealt with by ordinary control statements.
Similarly although all classes and libraries (like the stupid Stack) should be written to protect themselves from abuse by raising exceptions if impossible things are expected of them, they should not be used
6 THE PHILOSOPHY OF HOW TO USE EXCEPTIONS
9
as control structures by client code. The client code should check the stack is not empty before removing
items, proper use of the stack by correct programs will not give rise to exceptions. They will only occur in
“exceptional” circumstances.
In conclusion the original intention should still be remembered:
programs should be written to avoid all possibility of exceptions, if they occur the program
should clean up, and exit gracefully.