Java SE 8 Documentation
Exercises

Handling Exceptions

An exception is an event, usually caused by an error, that abnormally interrupts the flow of program when it accidentally happens. In order to prepare for all of those events, the source code has to be decorated with continuous error handling, which would then make the whole code quite hard to read. Exception handling tries to alleviate this by introducing a language construct that makes it possible to describe algorithms in an ideal way with the respective error handlers placed in a separate block.

Here is the basic template for handling exceptions in Java:

int i, j;

// Encapsulation of critical statements.
try {
    // Critical statements.
    i = Integer.parseInt(args[0]);
}
// Exception handler branches for each type (optional)
// The first branch that matches the exception by the class hierachy
// (base class: Throwable) will get it.  It is possible to rethrow
// the exception.
catch (NumberFormatException e1) {
    // Exception handler: Do not let it be empty.
    System.err.println("Ooops, this was a number format exception.");
    System.err.println(e.getMessage());  // Error message to the standard error
    e.printStackTrace();                 // stack trace
    i = 0;
}
// Further branches.
catch (Exception e2) {
    // Exception handler for other exceptions.
    System.err.println("Hrm, some other error happened.");
    System.err.println(e.getMessage());
}
// Statements that will be executed after the critical block (optional)
finally {
    j = 42;
}

Every statement that trigger an exception when it is run will have to be put to a common block. If there is anything unexpected happens in this block (that is, an exception is thrown) in run time, the execution is interrupted and the control is passed over to one of the exception handlers. The handlers are to take care of this unexpected event in a well-behaved manner, for example their task is to notify the user of the program or to land and terminate the program in a controlled way. Such blocks are created with the try keyword, which refers to the fact that they may be interrupted any time.

Unexpected events are described by the java.lang.Throwable class in Java, which is the generic model of them. Every event may be given as one of the specialization of this class, that is, as a derived class. The name Throwable here reflects the terminology in Java, because exceptions may be thrown not raised. This is implemented by the throw statement that has to have a Throwable object as an argument. (So unexpected events are all represented by objects in this system.)

throw new Error("Error!");

And if exceptions are thrown they will have to be caught on the oher side, which is implemented by blocks with the catch keyword. The catch directly follows the try and it helps to tell how to deal with the various descendants of the Throwable class. Those classes have to be listed in an order where the most specialized ones appear first, and then mention the more and more generic ones if we want to handle many different types of them for the same block.

Types of Exceptions

There are many different types of exceptions:

  • Checked exceptions, java.lang.Exception. They have to be declared in the method headers with the throws keyword. And if they are declared then they will have to be handled. Otherwise the compiler will not accept the code.

    static boolean isErrorProne(int x) throws Exception {
        if (x > 42) throw new Exception("Yes, that is an error.");
        return false;
    }
  • Unchecked exceptions. They do not have to be either declared or handled. Examples of that are java.lang.NullPointerException, java.lang.ArrayIndexOutOfBoundsException, java.lang.NumberFormatException classes. Such events shall not really appear in case of properly implemented programs. But it is still useful to have them since they can help a lot in finding problems.

  • Critical exceptions, java.lang.Error. Such exceptions are not worth to handle, because once they have been thrown the chances are not likely that we will be able to restore the normal flow of program.

Hierarchy of exceptions

Hierarchy of exceptions

The pair of throw and catch is a little bit like the return statement, because once an exception is thrown, the execution of the current block of statements is interrupted and left behind. But in those cases not only the affected method will terminate but all the other program units that call to it, until the exception is handled. So, do not use this in place of return.

Holes in Exception Handling

It is recommended to consider the following on definition of exception handlers:

  • Aim to cover all the possible unexpected events. If we cannot map every one of them, use more generic versions, for example, the java.lang.Exception class. If we do not do that, the unexcepted event will show up at the upper layers of the program until it halts the execution of the entire program.

  • Keep exception handlers simple and short, because there may also happen unexpected events. That is, there may be exceptions thrown in the exception handlers themselves.

      int x, y;
      try {
          x = Integer.parseInt(args[0]);
          y = Integer.parseInt(args[1]);
      }
      catch (NumberFormatException nfe) {
          System.err.println("Either \"" + args[0] + "\" or \"" + args[1] + "\" is invalid.");
      }

Application of Exceptions: Input/Output

Unexpected events may happen mostly when handling input and output, hence one of the biggest fields of use for exceptions is that. In result, when handling files, it is very easy to meet methods that throw exceptions to catch.

import java.io.FileWriter;
import java.io.PrintWriter;

public class WriteSampleFile {
    public static void main(String[] args) throws Exception {
        PrintWriter pw = new PrintWriter(new FileWriter("dummy.txt"));
        pw.println("Enter your data here.");
        pw.close();
    }
}

In Java, input and output are organized into streams, in many different ways:

And those have further specialized versions, such as:

Streams may be grouped by purpose:

Basic Operations

In summary, now we briefly introduce the common file operations that are usually part of the respective classes. Here, note that once the file has been opened, a pointer is assigned to it that always hold the actual position of reading and writing. Files act like infinite lists that could be traversed sequentially.

Another important concept of implementation is the buffer. That is a block of memory, where certain (read or written) parts of the underlying file are stored temporarily, then moved to the disk. The goal is to improve performance, but it often results in a slightly different behavior. For instance, until the buffer is emptied, the file may not have any data inside. This must be forced by either placing more data to the buffer or calling the corresponding method.

The operations are as follows:

  • Open: That is automatic (done mostly on initialization).
  • Close: close().
  • Empty the associated file buffers: flush().
  • Skip a certain bytes of data, "jumping": skip().
  • Write data: write(), print().
  • Read data: read(), if there is no data in the stream yet, the reading operation will wait until it is present (it is blocked):

    public static void main(String[] args) throws Exception {
        int i = System.in.read();
        System.out.println("The character was just read: " + i);
    }
  • Bookmarks (where available): markSupported(), mark(), reset().
  • Check if the stream is empty (there is data available): ready().
  • Get the number of available bytes: available(), for example:

    int size = new FileInputStream("dummy.txt").available();

Examples

Write a file:

import java.io.FileNotFoundException;
import java.io.PrintWriter;

public class WriteFile {
    public static void main(String[] args) {
        PrintWriter pw = null;

        try {
            pw = new PrintWriter(args[0]);
            pw.println("Line 1");
            pw.println("Line 2");
        }
        catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        finally {
            if (pw != null)
                pw.close();
        }
    }
}

Read a file:

public class ReadFile {
    public class void main(String[] args) {
        BufferedReader br = null;

        try {
            br = new BufferedReader(new FileReader(args[0]));
            String line = null;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        }
        catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        finally {
            if (br != null) {
                try {
                    br.close();
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Direct Access

Unlike the previous approaches, java.io.RandomAccessFile implements a way that makes it possible to access files like they were an array of bytes, that is, in an arbitrary order. There is also a file pointer to store the actual position of reads and writes that could be queried (getFilePointer()) or set (seek()). The java.io.RandomAccessFile can act as both java.io.DataInput and java.io.DataOutput, values of arbitrary types may be read and written (so it could be used it was a java.io.DataInputStream or java.io.DataOutputStream): write(), and read*() methods), or the bytes may be skipped (skip()).

import java.io.IOException;
import java.io.RandomAccessFile;

public class RandomFileTest {
    public static void main(String[] args) throws IOException {
        RandomAccessFile raf = new RandomAccessFile("dummy.dat", "rw");

        raf.writeInt(0xCAFEBABE);
        raf.seek(16);
        raf.writeInt(0xDEADBEEF);
        raf.seek(32);
        raf.writeInt(0xBADF00D0);
        raf.seek(48);
        raf.writeInt(0xDEADC0DE);
        raf.close();
    }
}

Reading Text Files with java.util.Scanner

Note that the previously introduced java.util.Scanner can be also used for working with plain text files. All this needs is to call the corresponding constructor where the parameter is an object of type java.io.File. The java.io.File class is to represent files, and if one of its constructors is called with a String value, then that will be treated as a path, and will be communicated as a file to be processed by the java.util.Scanner object.

Though, important differences to the earlier use of java.util.Scanner are that there may be certain kind of exceptions thrown, and that the close() method has to be called once the processing is completed (otherwise the file used in the background will not be closed, and that will waste resources).

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Scanner;

public class FileScanner {
    public static void main(String[] args) throws IOException {
        Scanner sc = null;

        try {
            sc = new Scanner(new File(args[0]));
            while (sc.hasNextLine()) {
                String line = sc.nextLine();
                System.out.println(line);
            }
        }
        catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        finally {
            if (sc != null) {
                sc.close();
            }
        }
    }
}

Generating Numbers Pseudorandomly

Sometimes it might be useful to not to get input from the user or not to hardwire values as constants in the sources but create them at random. It is good to know that it is possible to generate pseudorandom numbers that enables us to simulate kind of a non-deterministic behavior in our programs.

  • The java.util.Random class can generate pseudorandom integers, floating-point numbers, Boolean values, etc. in a given range.

  • The java.lang.Math.random() static method is capable of generating pseudorandom floating-point numbers that are in between zero and one (normalized values).

From those, the use of java.util.Random is recommended. In addition to that, it is enough to instantiate this class only once and then call the corresponding method for getting pseudorandom numbers as many time as needed. But it is also possible to generate random numbers that are deterministic, that is, predictable, if the constructor of java.util.Random is called with a specific seed value on instantiation. In that case, the values read from the object will be random compared to each other, but the same sequence of random values will be associated to the same seed value.

For example, create a sequence of random numbers as specified by the user in the command line, giving their count and range.

import java.util.Random;

public class RandomInteger {
  public static void main(String[] args){
    int n, lowerBound, upperBound;

    try {
        n          = Integer.parseInt(args[0]);
        lowerBound = Integer.parseInt(args[1]);
        upperBound = Integer.parseInt(args[2]);
    }
    catch (Exception e) {
        System.err.println("Not enough or invalid parameters were given.");
        System.exit(1);
    }

    System.out.printf("Generating %d random integers in range [%d..%d]...",
      n, lowerBound, upperBound);

    Random randomGenerator = new Random();
    int range = (upperBound - lowerBound);

    for (int i = 0; i < n; ++i){
        int r = lowerBound + randomGenerator.nextInt(range);
        System.out.println("Generated: " + r);
    }
  }
}

Exercises

  • Write a program that takes two command-line arguments: a path for reading a file and a text. Let the program start to read the given file, then:

    • consider the text as a single word and count its occurences in the file.

    • filter out the lines from the contents of the file where the text appears and print them to the standard output.

  • Write a program that takes a path for writing a file, then:

    • record every line read from the standard input to it until an empty line is entered.

    • fill it with a number of random

      • characters.
      • bytes.

    Always check if the given file exists and overwrite when it does.

  • Create a DateGenerator class that has only a single public class-level method called generate(). In this method, build a new valid Date object on every invocation as a return value. Date objects are to represent dates where a year, month, and a day is stored in a read-only way.

    • Let there be a version of generate() where a given number of random Date objects are generated and returned together in an array.

    • Add a new method for the Date class that can turn the object's internal state into a sequence of bytes.

    • With the help of the previous method, implement a version of generate() where the dates are directly put in a file.

Related sources
Front page
Back