1
0

Errors and Exceptions in Java

Errors and Exceptions are related terms and indeed have many things in common. Both are undesirable and lead to unpredictable behavior in the program. But in Java, the term Exception is more specific and differs from Error in many ways.

Errors are the mistakes done by the programmer knowing and unknowingly and can be avoided by careful programming practices. Whereas Exceptions are exceptional situations that arise in the program by the action of the user and fault in the system. Normally exceptional situations can be anticipated by programmers but can not be avoided altogether.

For example, consider a simple situation. We want to evaluate the expression c=a/b; where a,b, and c are int type variables, and the value of a and b is given by the user. The expression c=a/b; is a perfectly right expression. There is no error in it, but if the user inputs b=0, everything will fail. The situation can not be termed as an error, because this expression works fine for all other inputs, so in Java, such situations are called Exceptions.

Types of Errors #

As we have already stated that errors are the mistakes done by programmers. So depending on when that mistake is caught, the errors can be classified into the following categories:

  1. Syntax Errors (caught at compile time)
  2. Semantic Errors (caught after executing the program)
  3. Runtime Errors (Normally called Exceptions)

Syntax Errors are also called programming grammatical errors. As we know that every Java statement has a well-defined syntax. Every token has a fixed place in the statements. If the programmer wrongly spells the keyword, put mismatching braces, do not put semicolons at the end of a statement then all these mistakes fall in the category of Syntax errors. But one thing that is good about these errors as the compiler has got the facility to identify these errors and report to the programmer about their presence. For the successful compilation of the program, all such errors need to be corrected.

Semantic Errors are also called logical errors. These errors are difficult to handle because the presence of these errors is sensed by the wrong results of the program. The program with semantic errors compiles successfully, and executes successfully but gives wrong results. This means the programmer has implemented some wrong logic in the program or some mistake has occurred in arithmetic or logical expressions. To remove such error programmer need to study the program thoroughly, try to take intermediate results, and analyze a different section of code separately.

Runtime Errors also called exceptions normally occur during program execution. When these errors are present in our program, it is likely that the program may compile successfully and sometimes may give the right results after execution. But for certain sets of input, the program stops abruptly by displaying some error messages on the screen. Such situations need to be identified and corrective measures may be taken for these errors. The process of controlling such errors is known as exception handling in Java.

Program to demonstrate the presence of exceptions

import java.io.*;
class Except01
{
    public satic void main(String str[])
    {
        int a, b, c;
        a = 90;
        b = 90;
        c = 0;
        System.out.println("First Number = " + a);
        System.out.println("Second Number = " + b);
        c = a/(a-b);                            // Exception Thrown
        System.out.println("Result = " + c);    // Will not get executed
    }
}

This program will get compiled successfully. But during execution, it will generate an error because it has Division by Zero exception.

Exception Classes #

Java Language is a robust language means that the Programs written in Java language do not break down even under exceptional conditions and continue execution in spite of run time errors. This is because Java provides a rich set of Exception classes and a very elegant mechanism for Exception handling. Here we are giving some common exception classes with the types of exceptions they deal with.

Arithmetic Exception: These exceptions are caused by mathematical errors in the statements such as divide by zero.

int a = 10, b = 5, c = 5;
int d = a/(b-c);    // causes ArithmeticException

ArrayIndexOutOfBoundsException: This exception is caused when the value of the array index lies outside the array index range.

int list[] = new int[10];
int k = -1;
list[k] = 20;   // causes ArrayIndexOutOfBoundsException
k = 5;
list[k] = 20;   // valid statement
k = 15;
list[k] = 20;   // causes ArrayIndexOutOfBoundsException

ArrayStoreException: This exception is caused when a statement tries to store the wrong type of data in an array.

int list[] = new int[10];
list[5] = 34.56f;   // causes ArrayStoreException

FileNotFoundException: This exception is caused when the program tries to access a file that does not exist.

DataInputStream file;
file = new DataInputStream(new FileInputStream("mydata.txt"));
// this statement will cause FileNotFoundException if mydata.txt file does not exist

IOException: This exception is caused by general input output failures, such as the inability to read from a file.

NullPointerException: This exception is caused when a statement in the program refers to a null object.

Student st; // st declared not created
st.inputData(); // causes NullPointerException

NumberFormatException: This exception is caused when a statement tries to convert a non-numeric string to a number.

String str = "India";
int num = Interger.parseInt(str);   // causes NumberFormatException

OutOfMemoryException: This exception is caused when there is not enough memory available to satisfy the memory allocation request.

SecurityException: This execution is caused when an applet tries to perform an action not allowed by the browser’s security settings.

StringIndexOutOfBoundsException: This exception is caused when a statement tries to access a non-existing character position in the string.

There are some more Exception classes related to Threads.

Exception Handling in Java #

An exception is an event, which occurs during program execution and disrupts the normal flow of the program’s instructions.

An exception is an unwanted situation that disturbs the normal execution of the program. These are run-time errors not caught by the compiler. There are many cases where abnormal conditions arise during program execution. Some of these are:

  • Input data is not in the desired format
  • In division expression, the denominator is 0
  • You are trying to open a non-existing file
  • Wrong array index, etc.

If these abnormal conditions are not handled properly then the program may result in undesirable behavior, such as abnormal termination or giving the wrong results.

Java exception handling mechanism is simple to understand. The statement that may contain exceptional conditions is written in the try block. This block is followed by one or more catch blocks and an optional finally block.

When the statements in the try block are executed and an abnormal condition occurs, an exception is thrown. This thrown exception is caught by the matching catch block and the statements in the corresponding catch block are executed. The ‘finally’ block if specified is always executed whether or not an exception occurs.

Throwing an exception is a better alternative than abnormal termination of the program because it provides the programmer with the option of writing a handler to deal with the abnormal condition.

Exception Handling Construct

The general format (Syntax) of exception handling construct is:

try                                     // try block
{
    statements that may cause exceptions
}
catch(ExceptionClass1 exceptionObject1) // catch block
{
    Exception handler program body1
}
catch(ExceptionClass2 exceptionObject2) // catch block
{
    Exception handler program body2
}
...
finally                                 // finally block
{
    Must exucute program body
}

The try keyword is used to specify the block of statements that may cause exceptions. These exceptions are handled by the succeeding catch clauses. There can be any number of catch blocks. When the exception is thrown, the body of the first catch block whose exception class type is the same class or super class of the thrown exception will be executed.

When an error occurs within a method, the method creates an object and hands it over to the runtime system. The object, called an exception object, contains information about the error, including its type and the state of the program when the error occurred. Creating an exception object and passing it to the runtime system is called throwing an exception.

After a method throws an exception, the runtime system attempts to find some method to handle it. The set of possible methods to handle the exception is the ordered list of methods that had been called to get to the method where the error occurred. The list of methods is known as the call stack (see the figure).

The call stack

The runtime system searches the call stack for a method that contains a block of code that can handle the exception. This block of code is called an exception handler. The search begins with the method in which the error occurred and proceeds through the call stack in the reverse order in which the methods were called. When an appropriate handler is found, the runtime system passes the exception to the handler. An exception handler is considered appropriate if the type of the exception object thrown matches the type that can be handled by the handler.

The exception handler chosen is said to catch the exception. If the runtime system exhaustively searches all the methods on the call stack without finding an appropriate exception handler, as shown in the next figure, the runtime system (and, consequently, the program) terminates.

Searching the all stack for the exception handler

Using exceptions to manage errors has some advantages over traditional error-management techniques. You can learn more in the Advantages of Exceptions section.

Program to demonstrate the exception handling in Java

import java.io.*;
class Except02
{
    public static void main(String str[])
    {
        int a, b, c;
        a = 90;
        b = 90;
        c = 0;
        System.out.println("First Number = " + a);
        System.out.println("Second Number = " + b);
        try
        {
            c = a/(a-b);
        }
        catch(ArithmeticException e)
        {
            System.out.println("Division by Zero Error");
        }
        System.out.println("Result = " + c);
    }
}

Now, this program will be executed successfully and will give the following output:

First Number = 90
Second Number = 90
Division by Zero Error
Result = 0

Although it is not the expected result in spite of the presence of error, the program does not break down and completes its execution. Note that the statement that causes the error is written in a try block. When this statement is executed it throw an object of ArithmeticException class which is caught and processed by the following catch block after this program continues to execute normally.

Use of try, catch, finally, throw, and throws #

The try Block

The first step in constructing an exception handler is to enclose the code that might throw an exception within a try block. In general, a try block looks like the following.

try
{
    code
}
catch and finally blocks...

The segment in the example labeled code contains one or more legal lines of code that could throw an exception. (The catch and finally blocks are explained in the next two subsections.)

To construct an exception handler for the writeList method from the ListOfNumbers class, enclose the exception-throwing statements of the writeList method within a try block. There is more than one way to do this. You can put each line of code that might throw an exception within its own try block and provide separate exception handlers for each. Or, you can put all the writeList code within a single try block and associate multiple handlers with it. The following listing uses one try block for the entire method because the code in question is very short.

private Vector vector;
private static final int SIZE = 10;
PrintWriter out = null;
try
{
    System.out.println("Entered try statements");
    out = new PrintWriter(new FileWriter("OutFile.txt"));
    for(int i=0; i<SIZE; i++)
    {
        out.println("Value at: " + i + " = " + vector.elementAt(i));
    }
}
catch and finally statements....

If an exception occurs within the try block, that exception is handled by an exception handler associated with it. To associate an exception handler with a try block, you must put a catch block after it; the next section shows you how.

The catch Blocks

You associate exception handlers with a try block by providing one or more catch blocks directly after the try block. No code can be between the end of the try block and the beginning of the first catch block.

try
{
}
catch(ExceptionType name)
{
}
catch(ExceptionType name)
{
}

Each catch block is an exception handler and handles the type of exception indicated by its argument. The argument type, ExceptionType, declares the type of exception that the handler can handle and must be the name of a class that inherits from the Throwable class. The handler can refer to the exception with name.

The catch block contains code that is executed if and when the exception handler is invoked. The runtime system invokes the exception handler when the handler is the first one in the call stack whose ExceptionType matches the type of the exception thrown. The system considers it a match if the thrown object can legally be assigned to the exception handler’s argument.

The following are two exception handlers for the writeList method – one for two types of checked exceptions that can be thrown within the try statement.

try
{
    
}
catch(FileNotFoundException e)
{
    System.err.println("FileNotFoundException: " + e.getMessage());
    throw new SampleException(e);
}
catch(IOException e)
{
    System.err.println("Caught IO Exception: " + e.getMessage());
}

Both handlers print an error message. The second handler does nothing else. By catching any IOException that’s not caught by the first handler, it allows the program to continue executing.

The first handler, in addition to printing a message, throws a user-defined exception. (Throwing exceptions is covered in detail later in this chapter in the How to Throw Exceptions section.) In this example, when the FileNotFoundException is caught it causes a user-defined exception called SampleException to be thrown. You might want to do this if you want your program to handle an exception in this situation in a specific way.

Exception handlers can do more than just print error messages or halt the program. They can do error recovery, prompt the user to make a decision, or propagate the error up to a higher-level handler using chained exceptions, as described in the Chained Exceptions section.

The finally Block

The finally block always executes when the try block exits. This ensures that the finally block is executed even if an unexpected exception occurs. But finally is useful for more than just exception handling – it allows the programmer to avoid having cleanup code accidentally bypassed by a return, continue, or break. Putting cleanup code in a finally block is always a good practice, even when no exceptions are anticipated.

The try block of the writeList method that you’ve been working with here opens a PrintWriter. The program should close that stream before exiting the writeList method. This poses a somewhat complicated problem because writeList’s try block can exit in one of three ways.

  • The new FileWriter statement fails and throws an IOException.
  • The vector.elementAt(i) statement fails and throws an ArrayIndexOutOfBounds Exception.
  • Everything succeeds and the try block exits normally.

The runtime system always executes the statements within the finally block regardless of what happens within the try block. So it’s the perfect place to perform the cleanup.

The following finally block for the writeList method cleans up and then closes the PrintWriter.

finally
{
    if(out != null)
    {
        System.out.println("Closing PrintWriter");
        out.close();
    }
    else
    {
        System.out.println("PrintWriter not open");
    }
}

In the writeList example, you could provide for cleanup without the intervention of a finally block. For example, you could put the code to close the PrintWriter at the end of the try block and again within the exception handler for ArrayIndexOutOfBoundsException, as follows.

try
{
    out.close();    // Don't do this; it duplicates code.
}
catch(FileNotFoundException e)
{
    out.close();    // Don't do this; it duplicates code.
    System.err.println("Caught: FileNotFoundException: " + e.getMessage());
    throw new RuntimeException(e);
}
catch(IOException e)
{
    System.err.println("Caught IOException: " + e.getMessage());
}

However, this duplicates code, thus making the code difficult to read and error-prone should you modify it later. For example, if you add code that can throw a new type of exception to the try block, you have to remember to close the PrintWriter within the new exception handler.

Note: The finally block is a key tool for preventing resource leaks. When closing a file or otherwise recovering resources, place the code in a finally block to insure that the resource is always recovered.

The throw Statement:

All methods use the throw statement to throw an exception. The throw statement requires a single argument: a throwable object. Throwable objects are instances of any subclass of the Throwable class. Here’s an example of a throw statement.

throw someThrowable Object;

Let’s look at the throw statement in context. The following pop method is taken from a class that implements a common stack object. The method removes the top element from the stack and returns the object.

public Object pop()
{
    Object obj;
    if(size == 0)
    {
        throw new EmptyStackException();
    }
    obj = objectAt(size - 1);
    setObjectAt(size - 1, null);
    size--;
    return obj;
}

The pop method checks to see whether any elements are on the stack. If the stack is empty (its size is equal to 0), pop instantiates a new EmptyStackException object (a member of java.util) and throws it. The Creating Exception Classes section in this article explains how to create your own exception classes. For now, all you need to remember is that you can throw only objects that inherit from the java.lang.Throwable class.

Note that the declaration of the pop method does not contain a throws clause. EmptyStackException is not a checked exception, so pop is not required to state that it might occur.

Using the throws keyword

In the previous examples, the exceptions were handled approximately where they occur. This can lead to some fairly complex code. For instance, if you are doing a large amount of I/O you will end up with huge amounts of try/catch blocks that can soon make your code hard to read.

The use of the throws keyword allows you to pass exceptions up the stack to calling methods. The throws keyword is appended to a method name and is followed by the Exceptions that might be thrown. Thus typically you might have a method with a file operation thus

public void amethod() throws IOException
{
    FileOutputStream fout = new FileOutputStream("test.txt");
    fout.close();
}

Notice how the throws clause comes after the parameter parenthesis and before the curly brackets. This sample code simply creates a file in the underlying operating system called test.txt. It is the close method of the FileOutputStream class that actually writes the file to the operating system. Without the throws clause, this code would not even compile. Note that the calling method must catch the declared exception.

Taking User Input #

In Java, the only way to perform console input was to use a byte stream, and older code that uses this approach persists. Today, using a byte stream to read console input is still technically possible, but doing so may require the use of a deprecated method, and this approach is not recommended. The preferred method of reading console input for Java is to use a character-oriented stream, which makes your program easier to internationalize and maintain. Java does not have a generalized console input method that parallels the standard C function scanf() or C++ input operators (cin).

So now let’s focus on input only. Output works in a similar way, and we have seen many examples till now. In this section, we will cover the following topics:

  • Character Input
  • Reading Strings from the Keyboard
  • Reading Numbers and other types from the Keyboard

All Java programs automatically import the java.lang package. This package defines a class called System, which encapsulates several aspects of the run-time environment. For example, using some of its methods, you can obtain the current time and the settings of various properties associated with the system. System also contains three predefined stream variables, in, out, and err. These fields are declared as public and static within System. This means that they can be used by any other part of your program without reference to a specific System object. System.out refers to the standard output stream. By default, this is the console.

System.in refers to standard input, which is the keyboard by default. System.err refers to the standard error stream, which also is the console by default. However, these streams may be redirected to any compatible I/O device. System.in is an object of type InputStream; System.out and System.err are objects of type PrintStream. These are byte streams, even though they typically are used to read and write characters from and to the console. As you will see, you can wrap these within character-based streams, if desired. The preceding chapters have been using System.out in their examples. You can use System.err in much the same way. As explained in the next section, the use of System.in is a little more complicated.

In Java, console input is accomplished by reading from System.in. To obtain a character-based stream that is attached to the console, you wrap System.in in a BufferedReader object, to create a character stream. BufferedReader supports a buffered input stream. Its most commonly used constructor is shown here:

BufferedReader(Reader inputReader)

Here, inputReader is the stream linked to the instance of BufferedReader being created. Reader is an abstract class. One of its concrete subclasses is InputStreamReader, which converts bytes to characters. To obtain an InputStreamReader object that is linked to System.in, use the following constructor:

InputStreamReader(InputStream inputStream)

Because System.in refers to an object of type InputStream, it can be used for inputStream. Putting it all together, the following line of code creates a BufferedReader that is connected to the keyboard:

BufferedReader temp = new BufferedReader(newInputStreamReader(System.in));

After this statement executes, temp is a character-based stream that is linked to the console through System.in.

We can also take the help of another wrapper class i.e. DataInputStream to make input possible i.e. using the following constructor:

DataInputStream Inp = new DataInputStream(System.in);

Then, user can input data with the help of built-in function inside DataInputStream class i.e. readLine(). For Example:

String Val = Inp.readLine(); // using above Inp object of
                            //DataInputStream class

Now we can convert Val in any form i.e. Integer, Char, float, etc using Integer, Float, Char, etc classes. Following is an example of taking age as input from the user:

Program for taking input from the user for the integer value.

import java.io.*;
class inputMethod
{
    public static void main(String s[])
    {
        int age;
        try
        {
            BufferedReader Inp = new BufferedReader(new InputStreamReader(System.in));
            // DataInputStream Inp = new DataInputStream(System.in);
            System.out.print("Enter your age: ");
            System.out.flush();
            String val = Inp.readLine();
            System.out.println("Value Entered in the form of String = " + val);
            age = Integer.parseInt(val);
            System.out.println("Your age in Integer form = " + age);
            age = age + 30;
            System.out.println("Mdified age = " + age);
        }
        catch(IOException e)
        {
            System.out.println("I/O Error");
            System.exit(1);
        }
    }
}

The output produced by this program is shown below:

Enter your age: 20
Value Entered in the form of String = 20
Your age in Integer form = 20
Mdified age = 50

In the above program if you use DataInputStream Inp new DataInputStream (System.in); in place of BufferedReader Inp = new BufferedReader(new InputStreamReader (System.in)); the program will be compiled with warning But you will get same result.

Program to demonstrate the Use of BufferedReader to read characters, integers, floats, and strings from the console.

import java.io.*;
class InputMethod2
{
    public static void main(String s[])
    {
        char sex_code = ' ';
        int age = 0;
        String name = new String();
        float weight = 0.0f;
        try
        {
            InputStreamReader inp = new InputStreamReader(System.in);
            BufferedReader br = new BufferedReader(inp);
            System.out.print("Enter your Name: ");
            name = br.readLine();
            System.out.print("Enter your Age: ");
            age = Integer.parseInt(br.readLine());
            System.out.print("Enter your Sex_Code (M/F): ");
            sex_code = br.readLine().charAt(0);
            System.out.print("Enter your Weight: ");
            weight = Float.parseFloat(br.readLine());
        }
        catch(IOException e)
        {
            System.out.println("IO Error");
        }
        System.out.println("Your Information that I received is: ");
        System.out.println("Your Name: " + name);
        System.out.println("Your Age: " + age);
        if(sex_code == 'M' || sex_code == 'm')
            System.out.println("You are a Male");
        else
            System.out.println("You are a Female");
        System.out.println("Your Weight is: " + weight + " kg ");
    }
}

The output produced by this program is shown here:

Enter your Name: xalgord
Enter your Age: 20
Enter your Sex_Code (M/F): M
Enter your Weight: 65
Your Information that I received is: 
Your Name: xalgord
Your Age: 20
You are a Male
Your Weight is: 65.0 kg 

Program for user input of data using parsing methods

import java.io.*;
class input1
{
    public static void main(String s[])
    {
        InputStreamReader inp = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(inp);
        try
        {
            System.out.print("Enter the age of the student: ");
            System.out.flush();
            String num1 = br.readLine();
            int age = Integer.parseInt(num1);
            System.out.print("Enter the fee of the student: ");
            System.out.flush();
            String num2 = br.readLine();
            float fee = Float.parseFloat(num2);
            System.out.print("Enter the phone number of the student: ");
            System.out.flush();
            String num3 = br.readLine();
            long phone = Long.parseLong(num3);
            System.out.println("Age = " + age);
            System.out.println("Fee = " + fee);
            System.out.println("Phone No. = " + phone);
        }
        catch(IOException e)
        {
            System.out.println("I/O Error");
            System.exit(1);
        }
    }
}

The output produced by this program is shown here:

Enter the age of the student: 20
Enter the fee of the student: 27000
Enter the phone number of the student: 8837504247
Age = 20
Fee = 27000.0
Phone No. = 8837504247

Program to compute simple interest using user input.

import java.io.*;
class SimpleInterest
{
    public static void main(String s[])
    {
        InputStreamReader inp = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(inp);
        try
        {
            System.out.println("Enter the Principal Amount: ");
            System.out.flush();
            String val1 = br.readLine();
            float principal = Float.valueOf(val1);
            System.out.println("Enter the Time: ");
            System.out.flush();
            String val2 = br.readLine();
            int time = Integer.valueOf(val2);
            System.out.println("Enter the Rate of Interest: ");
            System.out.flush();
            String val3 = br.readLine();
            float rate = Float.valueOf(val3);
        }
        catch(IOException e)
        {
            System.out.println("I/O Error");
        }
        float simple = (principal * rate * time) / 100;
        System.out.println("\nSimple Interest = " + simple);
    }
}

The output produced by this program is shown here:

Enter the Principal Amount:
2000
Enter the Time:
10
Enter the Rate of Interest:
8
Simple Interest:
1600

Command Line Arguments #

Sometimes you want to pass information into a program at the time of execution. This is accomplished by passing command-line arguments to main(). A command-line argument is information that directly follows the program’s name on the command line when it is executed. To access the command-line arguments inside a Java program is quite easy-they are stored as strings in the String array passed to the main(). For example, the following program displays all of the command-line arguments that it is called with:

Program to implement command-line arguments.

class CommandLine
{
    public static void main(String s[])
    {
        for(int i=0; i<s.length;i++)
            System.out.println("args[" + i + "]: " + s[i]);
    }
}

Try executing this program, as shown here:

C:\jdk2\bin\javac CommandLine.java
C:\jdk2\bin\java CommandLine this is a test 100-1

When you do, you will see the following output:

args[0]: this
args[1]: is
args[2]: a
args[3]: test
args[4]: 100
args[5]: -1

Advantages of Exceptions #

Now that you know what exceptions are and how to use them, it’s time to learn the advantages of using exceptions in your programs.

  • Separating Error-Handling Code from “Regular” Code

Exceptions provide the means to separate the details of what to do when something out of the ordinary happens from the main logic of a program. In traditional programming, error detection, reporting, and handling often lead to confusion. For example, consider the pseudocode method here that reads an entire file into memory.

readFile()
{
    open the file;
    determine its size;
    allocate that much memory;
    read the file into memory;
    close the file;
}

At first glance, this function seems simple enough, but it ignores all the following potential errors.

  • What happens if the file can’t be opened?
  • What happens if the length of the file can’t be determined?
  • What happens if enough memory can’t be allocated?
  • What happens if the read fails?
  • What happens if the file can’t be closed?

To handle such cases, the readFile function must have more code to do error detection, reporting, and handling. Here is an example of what the function might look like.

errorCodeType readFile()
{
    initialize errorCode = 0;
    open the file;
    if(theFileIsOpen)
    {
        determine the length of the file;
        if(gotTheFileLength)
        {
            allocate that much memory;
            if(gotEnoughMemory)
            {
                read the file into memory;
                if(readFailed)
                {
                    errorCode = -1;
                }
            }
            else
            {
                errorCode = -2;
            }
        }
        else
        {
            errorCode = -3;
        }
        close the file;
        if(theFileDidntClose && errorCode == 0)
        {
            errorCode = -4;
        }
        else{
            errorCode = errorCode and -4;
        }
    }
    else
    {
        errorCode = -5;   
    }
    return errorCode;
}

There’s so much error detection, reporting, and returning here that the original seven lines of code are lost in the clutter. Worse yet, the logical flow of the code has also been lost, thus making it difficult to tell whether the code is doing the right thing: Is the file really being closed if the function fails to allocate enough memory? It’s even more difficult to ensure that the code continues to do the right thing when you modify the method three months after writing it. Many programmers solve this problem by simply ignoring it – errors are reported when their programs crash.

Exceptions enable you to write the main flow of your code and to deal with exceptional cases elsewhere. If the readFile function used exceptions instead of traditional error-management techniques, it would look more like the following.

readFile()
{
    try
    {
        open the file;
        determine its size;
        allocate that much memory;
        read the file into memory;
        close the file;
    }
    catch(fileOpenFailed)
    {
        doSomething;
    }
    catch(sizeDeterminationFailed)
    {
        doSomething;
    }
    catch(memoryAllocationFailed)
    {
        doSomething;
    }
    catch(readFailed)
    {
        doSomething;
    }
    catch(FileCloseFailed)
    {
        doSomething;
    }
}

Note that exceptions don’t spare you the effort of doing the work of detecting, reporting, and handling errors, but they do help you organize the work more effectively.

  • Propagating Errors Up the Call Stack

The second advantage of exceptions is the ability to propagate error reporting up the call stack of methods. Suppose that the readFile method is the fourth method in a series of nested method calls made by the main program: method 1 calls method2, which calls method3, which finally calls readFile.

method1()
{
    call method2();
}
method2()
{
    call method2();
}
method3()
{
    call readFile();
}

Suppose also that method is the only method interested in the errors that might occur within readFile. Traditional error-notification techniques force method2 and method3 to propagate the error codes returned by readFile up the call stack until the error codes finally reach method 1 – the only method that is interested in them.

method1()
{
    errorCodeType error();
    error = call method2();
    if(error)
        doErrorProcessing;
    else
        proceed;
}
errorCodeType method2()
{
    errorCodeType error;
    error = call method3();
    if(error)
        return error;
    else
        proceed;
}
errorCodeType method3()
{
    errorCodeType error;
    error = call readFile();
    if(error)
        return error;
    else
        proceed;
}

Recall that the Java runtime environment searches backward through the call stack to find any methods that are interested in handling a particular exception. A method can drop any Exceptions thrown within it, thereby allowing a method farther up the call stack to catch it. Hence, only the methods that care about errors have to worry about detecting errors.

method1()
{
    try
    {
        call method2();
    }
    catch(exception e)
    {
        doErrorProcessing;
    }
}
method2() throws exception
{
    call method3();
}
method3() throws exception
{
    call readFile();
}

However, as the pseudo-code shows, dropping an exception requires some effort on the part of the middleman methods. Any checked exceptions that can be thrown within a method must be specified in its throws clause.

  • Grouping and Differentiating Error Types

exa Because all exceptions are thrown within a program are objects, the grouping or categorizing of exceptions is a natural outcome of the class hierarchy. An example of a group of related exception classes in the Java platform is those defined in java.io – IOException and its descendants. IOException is the most general and represents any type of error that can occur when performing I/O. Its descendants represent more specific errors. For example, FileNotFoundException means that a file could not be located on disk.

A method can write specific handlers that can handle a very specific exception. The FileNotFoundException class has no descendants, so the following handler can handle only one type of exception.

catch(FileNotFoundException e)
{
    ...
}

A method can catch an exception based on its group or general type by specifying any of the exception’s superclasses in the catch statement. For example, to catch all I/O exceptions, regardless of their specific type, an exception handler specifies an IOException argument.

catch(IOException e)
{
    ...
}

This handler will be able to catch all I/O exceptions, including FileNotFoundException, EOFException, and so on. You can find details about what occurred by querying the argument passed to the exception handler. For example, use the following to print the stack trace.

catch(IOException e)
{
    e.printStackTrace();    // Output goes to System.err.
    e.printStackTrace(System.out);    // Send trace to stdout.
}
You could even set up an exception handler that handles any Exception with the handler here.
catch(Exception e)
{
    // A (too) general exception handler
    ...
}

The Exception class is close to the top of the Throwable class hierarchy. Therefore, this handler will catch many other exceptions in addition to those that the handler is intended to catch. You may want to handle exceptions this way if all you want your program to do, for example, is print out an error message for the user and then exit.

In most situations, however, you want exception handlers to be as specific as possible. The reason is that the first thing a handler must do is determine what type of exception occurred before it can decide on the best recovery strategy. In effect, by not catching specific errors, the handler must accommodate any possibility. Exception handlers that are too general can make code more error-prone by catching and handling exceptions that weren’t anticipated by the programmer and for which the handler was not intended.

As noted, you can create groups of exceptions and handle exceptions in a general fashion, or you can use the specific exception type to differentiate exceptions and handle exceptions in an exact fashion.

Leave a Reply