Lab: Throwing and Catching Exceptions

Steven Zeil

Last modified: Jul 23, 2015
Contents:

In this lab, we’ll explore the use of Exceptions.

1 Unchecked Exceptions

In this section, we’ll explore unchecked exceptions, the run-time errors that generally arise from basic programming errors or misuse of an API. These are the runtime errors that we don’t plan for, and therefor Java does not require us to have code to catch these.

  1. Open Eclipse. Create a new Java project, “LineReader”. Eventually, we will be developing a simple program to read and print the first line of text from a file, but for now we’re going to explore some more basic issues.

  2. Create a new class “RuntimeErrors”. Add the following code:

    public class RuntimeErrors {
    
       public void doSomething(String s)
       {
    	  System.out.println ("in doSomething");
       }
    
       public static void main (String[] args)
       {
    	  System.out.println ("main has started");
    	  new RuntimeErrors().doSomething(null);
    	  System.out.println ("returned to main");
       }
    }
    

    Run the program and make sure that it works as expected.

  3. Now, let’s add some code:

    public class RuntimeErrors {
    
       public void doSomething(String s)
       {
          System.out.println ("in doSomething");
          System.out.println(s.length());
       }
    
       public static void main (String[] args)
       {
    	  System.out.println ("main has started");
    	  new RuntimeErrors().doSomething(null);
    	  System.out.println ("returned to main");
       }
    }
    

    Run the program and examine the exception message carefully. Notice that it tells you 3 distinct things:

    1. The name of the error: NullPointerException

    2. Where the error occurred: at a specific line of the file RunTimeError.java, in the function doSomething.

    3. Where the function doSomething was called from: from a specific line of the file RunTimeError.java, in the function main.

    In general, an exception message may have many lines, listing the entire chain of calls from the point of the error all the way back to main.

    Why did we get this error? There’s nothing illegal about passing “null” to a function that expects a String. Recall that all variables (including parameters) of class types in Java are actually referneces/pointers, and null is a perfectly legal value for a pointer.

    The error arises from the call “s.length()” - because s is, in this particular instance, null, it’s not legal to dereference it and try to get access to one of its members.

  4. Change the code again:

    public class RuntimeErrors {
       public void doSomething(String s)
       {
    	  System.out.println ("in doSomething");
    	  String[] array = new String[10];
    	  System.out.println(array.length);
    	  System.out.println(array[0].length());
       }
    
       public static void main (String[] args)
       {
    	  System.out.println ("main has started");
    	  new RuntimeErrors().doSomething(null);
    	  System.out.println ("returned to main");
       }
    }
    

    Run this code. Note that the “array.length” evaluation is OK. array is not null at that point in the function. However, its elements are null until initialized. Hence, the exception.

  5. Let’s add a loop:

    public class RuntimeErrors {
       public void doSomething(String s)
       {
    	  System.out.println ("in doSomething");
    	  String[] array = new String[10];
    	  array[0] = "";
    	  for (int i = 1; i <= 10; ++i) {
    	     array[i] = array[i-1] + i;
    	     System.out.println ("" + i + ": " + array[i]);
    	  }
       }
    
       public static void main (String[] args)
       {
    	  System.out.println ("main has started");
    	  new RuntimeErrors().doSomething(null);
    	  System.out.println ("returned to main");
       }
    }
    

    Run this. A different exception occurs this time. The name is descriptive enough to explain what has happened (and the fact that it includes the actual index value in the message is a nice touch).

    Correct this by changing the “<=” to a simple “<”. Run the program again to verify that it no longer crashes.

  6. Now let’s try adding some numeric calculation to the mix:

    public class RuntimeErrors {
       public void doSomething(String s)
       {
    	  System.out.println ("in doSomething");
    	  String[] array = new String[10];
    	  array[0] = "";
    	  for (int i = 1; i <= 10; ++i) {
    	     array[i] = array[i-1] + i;
    		 System.out.println ("" + i + ": " + array[i]);
    		 int len1 = array[i-1].length();
    		 int len2 = array[i].length();
    		 double ratio = ((double)len2) / len1;
    		 System.out.println("This is " + ratio + " times larger.");
    	  }
       }
    
       public static void main (String[] args)
       {
          System.out.println ("main has started");
          new RuntimeErrors().doSomething(null);
          System.out.println ("returned to main");
       }
    }
    

    Run this. Were you perhaps expecting an exception to be thrown? The first time that we calculate ratio, len1 will be zero, so we had a division by zero - but no exception. Look at the output from that case. Surprise! In IEEE floating point numbers, there is a perfectly legitimate value called “Infinity”, so this calculation is not considered to be illegal.

    Now, delete the type case “(double)” from that calculation. This causes the calculation of len2/len1 to be done in integer arithmetic rather than in floating point. Try running this version.

2 Checked Exceptions

In this section, we’ll look at checked exceptions. These are the exceptions that we plan for, ones where the developers of a class or the API anticipated that unusual or undesired behaviors might occur that applications would want to react to and perhaps recover from.

You may find it useful to keep a window open on the Java API during this lab.

  1. Return to your Eclipse LineReader project. Create a new class LineReader. Start it off with the following code:

    import java.io.*;
    
    
    public class LineReader {
    
    private File file;
    
    
       public LineReader(String fileName) {
          file = new File(fileName);
       }
    
    
    
       public static void main (String[] args)
       {
    	  if (args.length != 1) {
    	     System.err.println ("You must supply a file name (path) as a command argument.");
             return;
          }
    
          String fileName = args[0];
          new LineReader(fileName).listFile();
    
       }
    
    
       /**
       * Open the file (supplied in the constructor) and print
       * one or more lines from it.
       */
       private void listFile() {
          System.out.println ("Listing: " + file);
       }
    }
    

    This program expects to take a program from the command line. There are two ways that you can choose to handle this.

    • First, you can work within Eclipse. Right-click on the LineReader.java file in the Package Explorer tab. Select “Run as…Run Configurations”. If there is not already a configuration listed for LineReader, then select “Java Application” and then click on the “New launch configuration” symbol at the top of the left column.

    On the right, go to the Arguments tab. Under Program Arguments, type a file name or path to a file name. (If you wish, you can use either LineReader.java or RuntimeErrors.java. If your project uses the default Java project settings, these are actually being kept in the src/ directory, In that case, you would enter “src/LineReader.java” or “src/RuntimeErrors.java”.

    When you have made your entry, click on Run. This configuration (including your choice of file name for the command argument) will be retained as the mode for running LineReader until you change it.

    • Alternatively, you can launch your program from a command line. Use an ssh session or xterm if your are working under Linux, or a cmd window if you are working in MS Windows. Then cd to the location where the compiled .class files for your project are being kept.[^

    Yes, the cd command works in Windows cmd sessions as well as in Linux.

    ] Then run the program using a java command with the appropriate text file name:

    java LineReader pathToATextFile
    
  2. Now let’s work on opening the file so that we can read from it. Java has two distinct collections of I/O classes (in package java.io). The older collection is based on input and output streams. The newer collection, which we will use, is based on readers and writers.

    In both collections, specific types of readers/writers/streams are associated with different kinds of I/O devices. In this case, we want to read from a file, so we will use a FileReader. Make the following change to the listFile function:

    /**
    * Open the file (supplied in the constructor) and print
    * one or more lines from it.
    */
    private void listFile() {
       FileReader freader = new FileReader (file);
       System.out.println ("Listing: " + file);
    }
    

    Note that Eclipse immediately signals an error on the new line. Check the error message, and it tells you that you have an unhandled exception of type FileNotFoundException. To understand this, go to the browser window where you have the Java API. Find the package java.io, then the class FileReader. Then look at the constructor summary, find the constructor that takes a single parameter of type File, and click on it to see the detailed description. You can see that it is indeed documented that this constructor can throw the indicated exception.

    The error that we are looking at is the compiler’s enforcement of the “Catch or Specify Requirement” discussed in the readings.

  3. Back in Eclipse, click on the small red error indicator to the left of the new line. Eclipse will suggest two different fixes for this error. One is to surround the line with a try/catch construct. That’s the catch option. For now though, select the option to “Add throws declaration”.

    Look at the change that was made to the listFile() declaration. This is the “Specify” option in “Catch or Specify”. If we don’t want to deal with this possible exception in listFile(), then we can “punt” by simply announcing to the world that listFile() sometimes generated this particular run-time exception.

    Unfortunately, if you look on the right of the editor, you will see a new red mark. Scroll up and you will find that a new error message has been issued within main(). Check the message, and you will see that now the compiler is complaining that main() does not handle the FileNotFoundException. Left-clicking on the error mark, you will find the same two options offered. Again, let’s select “Add throws declaration”. Look at the change to main()’s declaration.

    Try running this program. Change the command line argument that you use both a path to an existing file and a path to a file that does not exist. Observe what happens when the exception is thrown and nothing catches it.

  4. Delete the two “throws FileNotFoundException” phrases so that we are back to where we were at the start of step 3. Click on the small red error indicator and select the option to “Surround with try/catch”.

    The automatically generated catch block isn’t bad. It would be useful in many contexts. But it’s output is identical to what we have been seeing for uncaught exceptions, so let’s change it to something a bit more distinctive:

    /**
    * Open the file (supplied in the constructor) and print
    * one or more lines from it.
    */
    private void listFile() {
       try {
    	  FileReader freader = new FileReader (file);
       } catch (FileNotFoundException e) {
    	  System.err.println ("Could not find file " + file);
    	  System.exit(1);
       }
       System.out.println ("Listing: " + file);
    }
    

    Run the program, supplying a non-existent file name, and observe that our catch code really does get executed.

  5. Now let’s move on and actually try to read a line of text. The FileReader class is not particularly convenient for this, as it is best suited to reading a block of bytes whose size is known beforehand. Much more convenient for our purposes is a BufferedReader. Read the Java API description of the BufferedReader class.

    BufferedReader is actually designed to “wrap” around a device-specific reader of some kind, so we can try using it something like this:

    /**
    * Open the file (supplied in the constructor) and print
    * one or more lines from it.
    */
    private void listFile() {
       try {
          FileReader freader = new FileReader (file);
    	  BufferedReader in = new BufferedReader (freader);
    	  String line = in.readLine();
    	  System.out.println (line);
    	  in.close(); // also closes freader
    	} catch (FileNotFoundException e) {
          System.err.println ("Could not find file " + file);
          System.exit(1);
       }
    }
    
    

    Make these changes to your code.

    Alas, more unhandled exceptions pop up. Use either of the options we have explored to correct these. Run the program, with an existing text file name, and see that it does indeed list the first line from that file.

  6. For an additional challenge, try adding a loop to this function so that it lists all of the lines in the file instead of only the first one. This will require you to know when you are have read the last line of the file. Consult the Java API documentation on the readLine function in class java.io.BufferedReader to see how you can tell when this has occurred.