Experiments in Java


Session X3: Input, Output, Files, and Exceptions

You may have noted that many of our programs and classes depend on two key classes, SimpleInput and SimpleOutput. SimpleInput has been responsible for the inputs to our programs and SimpleOutput has been responsible for generating output from our programs.

Are these standard classes that come with all releases of Java? No, they were developed specifically for this lab manual. Then why are we using them? Because the input and output classes that come with Java do not provide many of the key features that beginning programmers need. Java's input and output facilities are designed primarily for experienced programmers.

However, you should now be ready to think about input and output from the perspective of a more experienced programmer. At the same time, you may find it easiest to continue using SimpleInput and SimpleOutput for most of your programming tasks, extending them when you need new facilities.

Output and System.out

How does the computer write to the screen? It turns out to involve a number of complex operations, operations which often differ significantly from computer to computer. In addition, the way you write to the screen differs from the way you write to a file, and the way you write information intended for human consumption may differ from the way you write information intended for computer consumption.

Hence, most programming languages provide a variety of methods and objects, in object-oriented languages, to simplify printing. In addition, most languages have an object or variable that corresponds to the standard output of a program: the output that typically goes to the screen.

In Java, System.out is the name of an object of class java.io.PrintStream that can be used to print values.

Unfortunately, the designers of Java realized that they had made some mistakes in their original design, and deprecated PrintStream in Java 1.1. By saying that PrintStream is deprecated, they mean that they are still maintaining the class, but recommend that you use a replacement class (in this case, java.io.PrintWriter). We will begin by using the deprecated class, and then consider how we might update code to use the replacement.

How do you print something with a PrintStream object? The same way you do with a SimpleOutput object. That is, you can call print and println methods. So, if we have a PrintStream called ps, we might print Hello World with

    ps.println("Hello World");

How do we print with System.out? Since it's an object in class PrintStream, we can simply write

    System.out.println("Whatever we want to print");

What if we want to follow the new Java recommendations, and use a PrintWriter instead of a PrintStream? First, we must convert the PrintStream to a PrintWriter. Fortunately, Java includes a constructor for PrintWriter that does just that. Hence, if we want to convert Java's standard output to a PrintWriter named out, we would write

    PrintWriter pw = new PrintWriter(System.out, true);

How do we print with a PrintWriter now that we have one? We call its print and println methods, as in

    pw.println("Even more text");

In Experiment X3.1 you will experiment with the different output mechanisms.

Starting to build SimpleOutput

Let us begin to consider how we might write a SimpleOutput class. Why might we want such a class, given that PrintWriter seems to be able to do most of the work for us? One reason is that it will permit us to more easily add functionality. For example, it is often useful to have a default constructor for an output object that will send output to standard output. PrintWriter does not include such a constructor. In addition, PrintWriter does not include a print method that works on arrays. Given our current state of knowledge, writing a new class is the only way to extend other classes.

What fields will SimpleOutput need? Since it is recommended that we print with PrintWriters, it will have a writer field that refers to the PrintWriter.

  /**
   * The base PrintWriter that does the real work.
   */
  protected PrintWriter writer;

Our default constructor will need to initialize this writer. What should we initialize it to? To standard output.

  /**
   * Build an object that can write to standard output.
   */
  public MyOutput() {
    writer = new PrintWriter(System.out, true);
  } // MyOutput()

Now we need to fill in the print and println methods. In general, they will be fairly simple, consisting of just a call to the corresponding function in PrintWriter. For example,

  /**
   * Print a string.
   */
  public void print(String str) {
    writer.print(str);
  } // print(String)

Unfortunately, as you will soon realize, you will need to create a print method for every primitive type. This means that you will do some busy work. At the same time, you will have the freedom to experiment with the methods. You may also learn that the base class doesn't work quite the way you expected. For example, PrintWriters typically buffer their output (often, just until you tell them to finish the current line with a println). This means that you must explicitly tell them ``okay, print it now!'' with the flush operation.

  /**
   * Print a string.
   */
  public void print(String str) {
    writer.print(str);
    writer.flush();
  } // print(String)

In Experiment X3.2 you will start to build your own output class.

Output files

We are now ready to consider one advantage of using our own class: we can extend the class to print to files instead of to the screen. How do we do this? We create a different kind of PrintWriter. This time, we'll create one that can write to files. That is, we will use a PrintWriter object whose print and println methods print to a specified file, rather than to the screen.

How do we build such an object and associate it with a file? It is a multiple step process. First, we must build a java.io.FileWriter that knows how to write to files. Next, we convert that FileWriter to a PrintWriter, again using one of the nifty constructors that PrintWriter provides. Our constructor might then look like

  /**
   * Create an object that can write to a file.
   */
  public MyOutput(String filename) {
    // Build a FileWriter to write to the given file
    FileWriter fw = new FileWriter(filename);
    // Convert it to a PrintWriter
    writer = new PrintWriter(fw, true);
  } // MyOutput

Unfortunately, this code has a significant problem. In particular, it is not at all clear what should happen if you call the FileWriter constructor with an invalid filename. How does FileWriter tell MyOutput that there was an error?

In Experiment X3.3 you will consider output to files.

Exceptions

In Java, when a method cannot complete its required tasks, it indicates an error by throwing an exception. An exception is simply an object that is used to indicate errors. In effect, exceptions permit methods to exit in two different ways: they can exit normally, returning a value when appropriate or they can exit abnormally, indicating an error.

Every language provides some facility for dealing with errors. Java's differs from most in that Java forces the programmer to decide, in advance, what to do if an error occurs. Therefore, if you call a method that can throw exceptions, Java requires you to indicate what to do if an exception is thrown. It may be that you know that the method will never throw exceptions (e.g., you always create FileWriter objects using a valid file name). Nonetheless, the Java compiler will require you to indicate how to handle the exception.

How do you indicate how to handle the exception? You put the call to the method in a try/catch clause. First, you indicate to Java that you know that an exception may occur by writing

try {
  code-that-may-throw-exceptions;
} // try

Next, you indicate what to do by catching the exception. A catch clause looks a little like a method definition, except that it has no name and the parameter is an exception. Later, you will learn that there are a variety of types of exceptions. For now, you can just catch generic Exception objects.

catch (Exception e) {
  what-to-do-if-the-error-occurs;
} // catch(Exception) 

Putting it all together, we might write

  /**
   * Create an object that can write to a file.
   */
  public MyOutput(String filename) {
    try {
      // Build a FileWriter to write to the given file
      FileWriter fw = new FileWriter(filename);
      // Convert it to a PrintWriter
      writer = new PrintWriter(fw, true);
    } // try
    catch (Exception e) {
      // What should we do?
    } // catch(Exception)
  } // MyOutput

You may note that we enclosed a number of lines in the try clause. This is because we only want the subsequent lines to be executed if the thing we are trying succeeds. In effect, control exits a try clause when the first exception occurs. No subsequent statements are then executed. In this particular case, we don't want to create a new PrintWriter unless we've successfully created the FileWriter.

But what should we do if we catch an exception? In some cases, it may be appropriate to print an error message to output, although such cases are rare. In this case, we're setting up output, so it makes no sense to try to print a message. Instead, we should use Java's specified mechanisms for indicating errors and throw our own exception. Note that we are basically passing the buck. If a method we use fails, then we will most likely fail. If we fail, the method that called us may fail. Eventually, we may reach a method that understands how to handle failure.

To throw an exception, you use the throw command. Typically, you throw a newly created exception. You can create exceptions using strings as parameters. The strings, in effect, describe what went wrong.

If you write a method that throws exceptions, you need to indicate this by writing throws Exception in the function header. For our constructor, we might write:

  /**
   * Create an object that can write to a file.
   */
  public MyOutput(String filename) 
    throws Exception {
    // Build a FileWriter to write to the given file
    try {
      FileWriter fw = new FileWriter(filename);
      // Convert it to a PrintWriter
      writer = new PrintWriter(fw, true);
    } // try
    catch (Exception e) {
      throw new Exception("Could not send output to '" + filename + "'");
    } // catch(Exception)
  } // MyOutput

Instead of creating a new exception, it would also be possible to just rethrow e (the exception thrown by the FileWriter constructor.

In Experiment X3.4 you will reconsider output to files.

Closing files

It is considered good programming practice to close every file that you open. By closing a file, you are indicating that you are done with the file. If you don't close files, Java will close them for you when you are done with the program. However, if you don't close files and then open them again, you may not get the results you expected.

How do you close files in Java? Every FileWriter (and, in fact, every other kind of Writer) provides a close method. All you need to do is call this method when you're done with the FileWriter. How do you know when you're done? You might add a close method to MyOutput or SimpleOutput and make it your practice to close any output object you create.

You can also take advantage of the finalize method to close files when you're done with them. However, this may not be the most reliable solution as finalize is not always called at appropriate times. (If you have not read about this method, don't worry; it's not very relevant to the current subject matter.)

Input

We've looked at some issues pertaining to output. But how do we deal with input? It turns out that some aspects of input are similar and others are different. As in the case of output, in order to do input from the keyword (or, more precisely, standard input), you will use a predefined value, in this case System.in. As in the case of output, you will most likely need to convert this InputStream to another class, in this case Reader.

What are the differences? Recall that there are a large number of print methods given by Writers. It turns out that Readers only provide methods for reading individual characters. Hence, we need to use more specialized versions and we need to find a way to convert characters or strings to numbers.

Input and System.in

System.in is Java's standard name for the standard input stream. If you read the documentation for Java, you will quickly learn that System.in is an InputStream. Input streams are intended to be used to read bytes rather than characters or strings. As you've seen from the programs that you've written, it is more common to read strings or numeric values.

We'll start with the simplest form of common input: reading strings. Java's BufferedReader class provides a readString() method that reads one line of text from input. Hence, we need to go from an InputStream to a BufferedInputReader. How? Once again, you need to do a series of conversions. This time, you will convert the InputStream to an InputStreamReader and then convert that InputStreamReader to a BufferedInputStream.

For example, if we use reader to refer to the BufferedReader that our input class will use, we might write the following constructor.

  /**
   * Create a new object that can read from standard input.
   */
  public MyInput() {
    // First, update System.in to a more modern class
    InputStreamReader tmp = new InputStreamReader(System.in);
    // Then, convert it to something that can read lines.
    this.reader = new BufferedReader(tmp);
  } // class MyInput

Once we have that constructor, we can add a readString method that simply calls reader's readString method, as in

    return reader.readLine();

In Experiment X3.5 you will begin creating your own input class.

Converting strings to other types

Unfortunately, BufferedReader only provides two input methods: read, which reads single characters, and readString, which reads one line of input text. What if we want to read an integer or other numeric value?

It turns out that the best solution is to read an input string and then convert that input string to an integer. How do you do conversion? As in many cases before, this will require a series of steps. First, you build a numeric object that corresponds to the string. For example, to build an Integer that corresponds to the string str, we would write

    Integer iobj = new Integer(str);

Similarly, to build a Float that corresponds to the same string, we would write

    Float fobj = new Float(str);

Both of these constructors can throw NumberFormatExceptions, so be careful to try the construction and catch any errors.

Next, we must convert the objects into the primitive types. We do this with the appropriate xxxValue method. For example,

    int i = iobj.intValue();
    float f = fobj.floatValue();

Putting this all together, we get

  /**
   * Read an integer on a line by itself.
   */
  public int readInt() {
    // Read a line of input
    String str = this.readString();
    // Convert it into an integer object
    try {
      Integer iobj = new Integer(str);
      // Convert it into an int value
      int i = iobj.intValue();
      return i;
    } // try to convert the string
    // Can't convert!
    catch (Exception e) {
      // Return a default value
      return -1;
    } // can't convert
  } // readInt()

or, more concisely,

  /**
   * Read an integer on a line by itself.
   */
  public int readInt() {
    // Read a line of input
    String str = this.readString();
    // Convert it into an integer object
    try {
      return (new Integer(str)).intValue();
    } // try to convert the string
    // Can't convert! Return a default value.
    catch (Exception e) {
      return -1;
    } // can't convert
  } // readInt()

You may have noted that readInt expects each numeric input to be on a line by itself. In the problems we explore a readInt method that can read an integer from part of a line.

In Experiment X3.6 you will experiment with reading integers.


Copyright (c) 1998 Samuel A. Rebelsky. All rights reserved.

Source text last modified Tue Oct 26 13:45:26 1999.

This page generated on Tue Oct 26 15:36:38 1999 by Siteweaver.

Contact our webmaster at rebelsky@math.grin.edu