Lecture 8

Input/Output and Exception Handling

So far we have been doing simple input and output from the terminal. In this lecture we will start using files and we will learn how to handle exceptional situations where for example the program cannot find the requested file or the format of the file is not the one that we expect.

We read input from the terminal by creating an instance of class Scanner. When we create the instance we pass System.in as a parameter and this is how the scanner knows that it must read from the terminal:

Scanner scanner = new Scanner(System.in);

The same class can be used with a regular file but in this case we must tell the scanner which file to use. This happens by first creating an instance of class 'File' which we after that pass as a parameter to the constructor of the scanner:

File file = new File("c:\\oopt\\input.txt");
Scanner scanner = new Scanner(file);

If you try to compile this code, however, the compiler will report this error:

Test.java:7: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
		Scanner scanner = new Scanner(file);

What the compiler is trying to tell you is that when you open a file for reading, it might happen that the file that you expect to find is not there anymore. Such situations are called exceptional and Java has built-in language features which makes it possible to find and handle such exceptional situations.

If you look at the documentation for the constructor of the class 'Scanner':

public Scanner(File source) throws FileNotFoundException

you will see the declaration that it throws FileNotFoundException. The idea is that if the constructor discovers that the file does not exist, then the normal execution flow will be canceled. Instead of completing the construction of the Scanner object, the constructor will instead create an object of class FileNotFoundException which will record the details of the encountered situation. After that the constructor throws the object as a signals for exceptional situation.

The compiler has seen the declaration throws FileNotFoundException and it refuses to compile the code because it sees that we have not handled the situation. The language has a construction which lets the programmers to detect and handle exceptions:

try {
    File file = new File("c:\\oopt\\input.txt");
    Scanner scanner = new Scanner(file);
    System.out.println("The file is opened");
} catch (FileNotFoundException e) {
    System.out.println(e.getMessage());
}

The construction try-catch declares that if the code after the try keyword generates a FileNotFoundException exception then the rest of the code will be stopped and the execution will be resumed with the code inside the catch block.

Immediatelly after the catch keyword we must declare the type of the exception that we handle together with a variable that will hold a reference to the exception itself. In our case, if the file is missing the scanner will create an exception object and it will fill it in with diagnostic information. For instance with the name of the file that is missing. The variable e declared in the catch block will be assigned to point to the exception object. We use the method getMessage() of this object to get the message describing the situation.

Note that if an exception occurs then the execution is immediatelly transfered to the corresponding catch block. This means that the message 'The file is opened' will be really printed only if the file was successfully opened. If it isn't then the execution will jump over the corresponding println statement. If everything goes smootly in the try block then the code in the catch blocks will never be executed. The control will continue with the next statement after the whole try-catch section.

Once the scanner object is created, we can use the regular methods next(), nextLine(), nextInt(), etc, for reading from the file. In the following example we suppose that the file contains a list of integers which we have to read and after that we print the square of each number:

try {
    File file = new File("c:\\oopt\\input.txt");
    Scanner scanner = new Scanner(file);
    
    while (scanner.hasNext()) {
        int n = scanner.nextInt();
        System.out.println(n*n);
    }
} catch (FileNotFoundException e) {
    System.out.println(e.getMessage());
}

Here the method hasNext() returns false when the whole file is consumed. The numbers in the file must be separated with a space or a new line. We print the square of every number on a new line.

Now try to intentionally introduce an error in the input file, i.e. instead of a number write something else in the file. What happens when you run the program? The result will be that during the execution we will get the error:

Exception in thread "main" java.util.InputMismatchException
 at java.util.Scanner.throwFor(Scanner.java:857)
 at java.util.Scanner.next(Scanner.java:1478)
 at java.util.Scanner.nextInt(Scanner.java:2108)
 at java.util.Scanner.nextInt(Scanner.java:2067)
 at Test.main(Test.java:12)

InputMismatchException is another kind of exception. It arises when we use a method like nextInt() but the next token in the input cannot be interpreted as a number. When the interpreter encounters an unhandled exception then the default action is that the type and the associated message for the exception are printed and the whole program is terminated. In addition the interpreter also prints the stack trace that lead to the exception. In this case we see that the error happened on line 12 in Test.java which called the method nextInt(). After nextInt() there are a few more calls to methods internal for the Java library and at the end the error was reported by the method throwFor. The stack trace is often invaluable for debugging the cause of an exception in a large program.

If we want to print a more user friendly error message, then we need to catch the InputMismatchException. We symply add one more catch clause in the try-catch block:

try {
    File file = new File("c:\\oopt\\input.txt");
    Scanner scanner = new Scanner(file);
    
    while (scanner.hasNext()) {
        int n = scanner.nextInt();
        System.out.println(n*n);
    }
} catch (FileNotFoundException e) {
    System.out.println(e.getMessage());
} catch (InputMismatchException e) {
    System.out.println("The input must be a number");
}

All exceptions are classes and as we know the classes are organized in a hierarchy. The class hierarchy plays an important role in exception handling. In our example we handle two exceptions - FileNotFoundException and InputMismatchException, but in reality the catch blocks will also fire if the exception is of any class that is subclass of FileNotFoundException or InputMismatchException accordingly. The class hierarchy can also be used to implement exception handlers which are very general. For a class to be usable as an exception it must directly or indirectly inherit the top level exception class which is simply called Exception. If we have a handler for the class Exception then it will fire for absolutely any exception. For example we can merge the two catch blocks into one:

try {
    File file = new File("c:\\oopt\\input.txt");
    Scanner scanner = new Scanner(file);
    
    while (scanner.hasNext()) {
        int n = scanner.nextInt();
        System.out.println(n*n);
    }
} catch (Exception e) {
    System.out.println(e.getMessage());
}

Note that although it is allowed to have this kind of catch-all handlers, this should be used sparingly. The general principle is that you should catch exceptions that are expected and that you know how to handle. Catching an arbitrary exception and after that ignoring it is a guaranteed receipe for hiding a potentially serious problem in the program.

Did you notice that while for the first exception, i.e. FileNotFoundException, it was the compiler that gave us an error and it refused to compile the program? At the same time the second exception, i.e. InputMismatchException, was reported only at runtime. The reason for the difference is again related to the class hierarchy for the exceptions. In general every method that throws an exception must declare so with the clause throws after the signature of the method. The compiler then checks that every method which calls the method in question either catches the exception or it declares the unhandled exception with a throws clause after its own definition. The only exception to this rules are the exceptions that are derived from the class RuntimeException. The runtime exceptions does not need to be declared with throws clauses and for them the compiler doesn't check whether they are handled. InputMismatchException is derived from RuntimeException and this is why we didn't notice the problem until we entered invalid data in the input file. The library designers have the freedom to choose either Exception or RuntimeException as the base class of their own exceptions and in this way different behaviour arises. (Note: RuntimeException is also a subclass of Exception).

There is one more thing that we have not done properly in our example. When we open a file for input/output then this consumes resources from the operating system. In particular there is a limit on how many files a given program can keep open at the same time. When we finish working with files we must be careful to always tell the operating system that we are done with it and now the allocated resources can be released. For this purpose that class Scanner has the method close which we must call at the end. We also need to be extra careful with code that can generate exceptions. For instance this is the naive way to close the scanner:

try {
    File file = new File("c:\\oopt\\input.txt");
    Scanner scanner = new Scanner(file);
    
    while (scanner.hasNext()) {
        int n = scanner.nextInt();
        System.out.println(n*n);
    }

    scanner.close();
} catch (Exception e) {
    System.out.println(e.getMessage());
}

However, what will happen if the file contains errors? There will be an exception in the loop and the program control will never go through the point where the scanner is closed. We can add extra call to the 'close' method in the catch blocks but this will duplicate part of the programing logic. For this kinds of situations, Java offers one more clause in the try-catch block. After the last catch block it is possible to add an optional finally block:

Scanner scanner = null;
try {
    File file = new File("c:\\oopt\\input.txt");
    scanner = new Scanner(file);
    
    while (scanner.hasNext()) {
        int n = scanner.nextInt();
        System.out.println(n*n);
    }
} catch (FileNotFoundException e) {
    System.out.println(e.getMessage());
} catch (InputMismatchException e) {
    System.out.println("The input must be a number");
} finally {
    if (scanner != null)
        scanner.close();
}

The finally block is always guaranteed to be executed. If the execution of the try block was successful then the finally block is executed immediately after the try block. If an error has occured then the corresponding catch block is executed and after that the finally block. Since the finally block is always executed, this is the place where we do all kind of cleanup, such as releasing allocated resources.

It is completely relevant to want to have your own kinds of exceptions. For the sake of the example, let's suppose that we want to accept only positive numbers in our input file. Whenever we encounter a non-positive number we want to throw an exception. The first step is to define a class for the new kind of exceptions:

public class NonPositiveNumberException extends Exception {
     private int number;

     public NonPositiveNumberException(int number) {
         this.number = number;
     }

     public int getNumber() {
         return number;
     }
}

Unless if we want to add some diagnostic information to the exception, we don't have to declare any methods or instance variables in the new exception class. In our case, however, we want to propagate the value of the number that has caused the exception. For this purpose we have declared the instance variable number and the method getNumber. Now we can check the sign of the numbers and we can throw exceptions:

Scanner scanner = null;
try {
    File file = new File("c:\\oopt\\input.txt");
    scanner = new Scanner(file);
    
    while (scanner.hasNext()) {
        int n = scanner.nextInt();

        if (n <= 0)
            throw new NonPositiveNumberException(n);

        System.out.println(n*n);
    }
} catch (FileNotFoundException e) {
    System.out.println(e.getMessage());
} catch (InputMismatchException e) {
    System.out.println("The input must be a number");
} catch (NonPositiveNumberException e) {
    System.out.println(e.getNumber()+" is not a positive number");
} finally {
    if (scanner != null)
        scanner.close();
}

The throwing itself happens with the operator throw. It takes as an argument an exception object and it aborts the normal execution by redirecting it to the closest handler for exceptions of the corresponding type. In this case the execution after throw will continue with the catch block for the exception NonPositiveNumberException.

The scanner in combination with a few try-catch clauses is a convenient way to read files. The opposite operation, i.e. writting to files is possible with the help of the class 'PrintWriter'. Its constructor takes as a parameter a file, and the created object provides the methods print and println which we already know how to use with the System.out object. Just like with the scanner, we must not forget to close the writer object at the end. This is actually even more important because while with the scanner in the worst case we might not release some resources, with the writer we might loose data. If the writer is not properly closed some of the data that we intended to write might remain unsaved. The solution is to always use a try-finally statement which will ensure that the writer object is closed. The following is a variation of the previous example which stores the computed values in a file instead of printing them to the screen:

Scanner scanner = null;
PrintWriter writer = null;
try {
    File in_file = new File("c:\\oopt\\input.txt");
    scanner = new Scanner(in_file);

    File out_file = new File("c:\\oopt\\output.txt");
    writer = new PrintWriter(out_file);

    while (scanner.hasNext()) {
        int n = scanner.nextInt();

        if (n <= 0)
            throw new NonPositiveNumberException(n);

        writer.println(n*n);
    }
} catch (FileNotFoundException e) {
    System.out.println(e.getMessage());
} catch (InputMismatchException e) {
    System.out.println("The input must be a number");
} catch (NonPositiveNumberException e) {
    System.out.println(e.getNumber()+" is not a positive number");
} finally {
    if (scanner != null)
        scanner.close();
    if (writer != null)
       writer.close();
}