Karel J. Robot
A Gentle Introduction to the Art of Object-Oriented Programming in Java

This manuscript has not been published. It is Copyright, Joseph Bergin.

 


10 Input, Output, and Exception Handling

In this Chapter we discuss getting information into and out of the computer.

10.1 File Output

We have seen how to send information to System.out so that it can be seen by the user. But suppose that we want to keep a permanent record in a file. We will need to give the file a name so that the operating system knows where to save our information, and we need to connect our program to the file.

Before we do this however, we need to make a decision. Is the information going to be read by a person or are we just saving it so that another program can read it back in? Java provides different classes for these purposes. In Java, Writers are used to store information in the form of readable characters and OutputStreams are used when the data needn't be read by people. Information written by a Writer can later be read by a Reader. Information written by an OutputStream will usually later be read by an InputStream.

A FileWriter, defined in the java.io package, will connect you to an external file.


FileWriter writer = new FileWriter("data.txt");

We can now write characters to the file named writer. There are two things to note about this, however. First is that the operating system may not be able to provide the file for us. If this happens we have an error and the Java system will "throw" an exception. More specifically, it will throw an IOException, also defined in java.io. Exceptions, throwing, and catching are explained in the next Section. In general, errors can occur with most forms of input and output.

The second thing to note is that a FileWriter isn't very convenient. We can write characters to it, but not much else. The integer 68 is internally encoded not as two characters, but as an int. If we try to write 68 to a FileWriter (writer.write(68);) something unexpected will occur. The character "D" will be written since 68 is the internal character code for a "D".

In Java, the input/output classes are each designed to do one job well and several classes are used together to do sophisticated things. The way in which the Java I/O classes work follows a general pattern called Decorator. Many of the I/O classes are used to decorate objects of another I/O class by providing some "fancy packaging." We looked at decorators for strategies in Chapter 4. Here we have a different kind of decorator, but the same idea exactly. A decorator for a Writer will itself be a writer and will provide some additional services that the object it decorates does not provide. Here, we would like to use a PrintWriter, which knows how to write most of the things known to a Java program. A print writer doesn't actually print, however. It just knows how to format the things you give it (like ints) and pass them to the writer it decorates. We must layer up a PrintWriter onto a FileWriter to get output to a file. This is quite easily done also, though it too may throw an IOException.


PrintWriter output = new PrintWriter(new FileWriter("data.txt"));

Notice that we have created two objects here. First we create a FileWriter for the file "data.txt", but we don't give that object a name. Instead we pass this as a parameter to the PrintWriter constructor. Therefore the PrintWriter will decorate the FileWriter. We can pass ints to the PrintWriter, which will translate them into characters and then pass them to the FileWriter, which will itself put them into the file.


output.print(68);

A PritntWriter can print ints, doubles, Strings, etc. It can also println any of these things, in which case an end of line mark will be added after the value is printed. The above will output the character 6 followed by the character 8 and these will appear in the file data.txt.

One important thing to note is that when you open a Writer or an OutputStream in a program, you need to close it before the program exits if you want the contents of the stream to be preserved.


output.close();

If you forget this here, you won't find the file when the program exits.

A PrintWriter can decorate either another Writer or an OutputStream. This means that you can write ints and doubles, for example, to data files as well as character files though the data will be stored as characters if you do this.

Another output decorator is BuffferedWriter. This class might be used if you only wanted to write Strings to a Writer, such as a FileWriter. It provides internal buffering that makes the output more efficient by holding on to the information we write until there is quite a bit of it and then writing it all at once. Since the physical write process to a file takes a long time, this can speed up a program. The last output decorator we will discuss here is the OutputStreamWriter which decorates an OutputStream, turning it into a Writer.

Sometimes you need to layer more than one decorator onto another object. Here we seldom use more than two decorators and one object that is not a decorator. The outermost decorator is the one you wish to actually use. The innermost Writer gives the final destination for the characters or other data you produce.

10.2 Exceptions

Errors occur in Java programs as in most human activity. When an error occurs in a running Java program we say than an exception (small e) has occurred. When this happens an Exception object (capital e) is created and "thrown". This can be caught or not, depending on the kind of Exception it is and on the program.

It is easy to generate an exception. Simply declare a new Robot variable and send it a message without actually creating any robot.


Robot John;
John.move();

This will generate a NullPointerException which will be thrown, but not caught. The program will terminate and we will get a message informing us of the fact, perhaps even telling us which line in our program contained the error. (Actually, it is just the thread that executes the error that terminates, not necessarily the entire program.)

We can catch this exception and therefore prevent the program from terminating. To catch an exception if it arises you include the statements that might throw the exception in a try block.


try
{	Robot John;
	John.move();
}
catch(NullPointerException e)
{	System.out.println(" A robot was not initialized: "  + e);
}

When we catch an exception we name the class of the exception we are interested in and give a variable that will have that type. Within the body of the catch clause we can send the Exception object messages, or print it out as here.

When an exception is thrown in a try block, the execution of the try is abandoned immediately and the system searches for a handler (a catch clause) of the type of the exception that was thrown.

If the code can throw more than one kind of exception, then you can have several catch clauses with a single try. The most specific ones should be listed first, and the more general ones later. Exceptions are objects from classes. Here, more general means a super class, and more specific means a sub class. This is because Exceptions behave polymorphically.

There are two basic kinds of exceptions in a Java program: Exception and RuntimeException. A null pointer exception is of the latter kind and these need not be caught. In fact, a correct program should never have any of these, so if you have one, you need to fix your program. In the above we need to make John refer to some robot before we send it any messages.

The other kind, Exception, is one that can occur even in a correct program. For example, when we try to create a file, the operating system may not permit it, perhaps because the disk is locked, or there is no space available. These kinds of problems are beyond the control of the programmer. Therefore, even a correct program can generate them. For this reason such exceptions must be caught by the programmer. The IOExceptions discussed in Section 10.1 are like this. So they must be caught.


PrintWriter output = null;
try
{ 	output = new PrintWriter(new FileWriter("data.txt"));
	...
}
catch(IOException e)
{	System.out.println(" File could not be created: "  + e);
	System.exit(1);
}

Here we have indicated with ellipsis that some things have been left out. The question arises as to how big a try block should be. This depends on whether you can recover from the exception or not. If you can't create a file, then you can't write to it, and perhaps your program can't continue. In this case, any statement that writes to the file should also be in the same try block. If it were outside, then we might be trying to write to a file that we haven't created. Here the variable output would still be null if the exception is thrown.

Another possibility, not applicable here, is to provide some default value for the information that was being created when the exception was thrown. If this can be reasonably done, then the program can continue, and the try block can be relatively short. This might be possible if ArithmeticException is thrown because we divide by zero.

In some circumstances, an exception must be caught, but it doesn't make sense to catch it at the point at which it was thrown. In this case we need to propagate it. For example, suppose we want to create a method to open an output file for us. It might make sense for the caller of this function to handle any exception thrown when it executes.


public PrintWriter openFile(String filename) throws IOException
{	return new PrintWriter(new FileWriter( s ));
}

Then, calls to this function should be enclosed in try blocks.

10.3 Input

Input is the opposite of output. We are trying to obtain information from somewhere for the program. The principles of use via decorators are the same as for output, but more exceptions may be thrown. In face, nearly every input statement might throw an exception since the source of the data might have become unavailable.

We can read from a file using a FileReader or FileInputStream, the former if the file contains character data. We can layer on a BufferedReader to read Strings from the file using the readLine method of BufferedReader. This will read an entire line of input, where it is assumed that the file is broken up into lines, perhaps because it was written using the println methods of a PrintWriter.

So far, so good, but what if we want to read other things besides Strings. As it turns out, there is no exact input analogue of a PrintWriter. Instead we use a Bufferedreader and its readLine method, and a special object called a StringTokenizer from the java.util package.

A StringTokenizer is an object that can break up a String into pieces separated by characters of our choice. To create a StringTokenizer initialized with information from a file, we might do something like the following.


try
{ 	BufferedReader input = new BufferedReader (new FileReader("data.txt"));	
	String str
	while((str = input.readLine()) != null)
	{	StringTokenizer tokens = new StringTokenizer(str);
		...
	}
}
catch(IOException e)
{	System.out.println(" File could not be opened: "  + e);
	System.exit(1);
}

Note that a readLine will return null at the end of the file. We use this fact to exit the loop. Notice that the while test contains an assignment to str as well as a test of the result. This seems like very strange practice, but it is a standard Java idiom, so you need to become familiar with it.

If we are inside the while loop, we have a non empty string, so we wrap a StringTokenizer around it to give us access to its parts.

Once we have the tokenizer we operate on it to get access to its string. We do this by writing a while loop controlled by calls to the tokenizer's hasMoreTokens method. We get access to an individual "word" or "token" in the string with the tokenizer's nextToken method. Continuing the above, and repeating part of it:

 

while((str = input.readLine()) != null)
{	StringTokenizer tokens = new StringTokenizer(str);
	while (tokens.hasMoreTokens())
	{ 	String word = tokens.nextToken();
		...
	}
}

Now, we can do anything we like with the String named Word. If we know that it encodes an int, for example, we can get that int with


	int ivalue = Integer.parseInt(word);

though this latter message may throw a NumberFormatException if the String doesn't actually contain an int value. Integer is a class in java.lang that is usually used to turn ints into objects, though it also provides this static method parseInt to translate Strings.

Notice that to do the above effectively, we need to know what to anticipate at each "word" in the file.

There is another variation on the StringTokenizer constructor in which we give it a string as the second parameter. The individual characters in this string will be taken as the ones that are used to break up the String. For example, we could have a String of works separated with slashes and tokenize this with a tokenizer whose second parameter was just "/". If we don't give this parameter, the default value is used with consists of the so-called white space characters: space, tab, and newline.

10.4 File At A Time Input

In some ways the most efficient way to read a file, assuming that it isn't absolutely HUGE, is to read it all at once into a buffer. The FileInputStream class lets us ask how big a file is with its available method. So we base this on that class, but we layer an InputStreamReader on top of it. This class will let us read a buffer (char array) of any size, so we create one of the size of the file. We then read the entire file into the buffer and then put the characters in the buffer into a new String. This we can wrap with a StringTokenizer.


	try
	{	FileInputStream f = new FileInputStream("republicOfPlato.txt");
		InputStreamReader read = new InputStreamReader(f);
		int bufsize = f.available();
		char [] text = new char [bufsize];
		read.read(text, 0, bufsize); // You have the entire file in this array. 
		System.out.print(text); // Show the file. 
			
		String s = new String(text); // Now the array is in a string
		StringTokenizer t = new StringTokenizer(s, "\r\n"); 
			// break up on return and newline characters. 
		while(t.hasMoreTokens())
			System.out.println(t.nextToken()); // show the file one line per line. 
	}
	catch(IOException e){System.out.println("Failed");}

Here we set up the tokenizer to break into lines by using the newline and return characters as token separators: "\r\n"

10.5 Character At A Time Input

Sometimes you really do need to process a file one character at a time. There are a couple of standard tricks for doing this. Here we will use a buffered reader for efficiency only, not to get access to its readLine method.


	try // reading a byte at a time
	{	BufferedReader b = new BufferedReader(new FileReader("republicOfPlato.txt "));
		int ch; // YES int;
		while((ch = b.read()) >= 0) // YES assignment. Note: the parens are required.
		{	char c = (char)ch; // cast to char
			... // Do whatever you like with c. 
		}
	}
	catch(IOException e){System.out.println("Failed");}

When we read the file b, we read an int, not a char. This is because an int (32 bits) is bigger than a char (16 bits) so the system can signal end of file with a negative value. We do the read in the control part of a while loop in which we again have an assignment to the int variable and a test for non negative.

Inside the loop, we know the read succeeded, but we must cast the int to a char to use the value that was read. This does seem tricky, but it is the standard way to read a character at a time from a file.

10.6 Important Ideas From This Chapter

input
output
exception
try - catch block
throw
reader
writer

10.7 Problem Set

1. Write and test a subclass of Robot that will print a trace of all robot actions to a file that the user can name when he or she creates the robot to be traced. The trace should consist of lines with commands like the following:


move
turnLeft
turnLeft
move
turnOff

2. Investigate the following classes in the reference literature: Integer, Double, PrintWriter, BufferedReader, FileReader, FileWriter, StringTokenizer. What constructors and public methods do they provide?