In this laboratory session, you will implement versions of the
SimpleInput and SimpleOutput classes that
we have used in previous experiments. Along the way, you will learn
about exceptions, one of Java's key mechanisms for dealing
with run-time errors. The goals of this laboratory are to
Your instructor will tell you which of the proposed experiments you are to perform.
Prerequisite skills:
SimpleInput and
SimpleOutput classes
Required files:
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.
System.outHow 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.
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.
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.
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.
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.)
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.
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.
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.
Name: ________________
ID:_______________
System.out
Step 1.
Using our reliable SimpleOutput class, write a program
that prints your name. Enter your code here.
Step 2.
Using System.out, write a program that prints your name.
Enter your code here.
Step 3.
What, if any, advantages do you see to using SimpleOutput?
Why do you think we provided such a class?
After entering your answer, you may want to read the note on the subject.
Step 4.
Using a PrintWriter, write a program that prints your name.
It is likely that you will need to convert System.out to
a PrintWriter with
PrintWriter out = new PrintWriter(System.out, true);
You will also need to import java.io.PrintWriter.
Enter your code here.
Step 5.
What do you see as the relative advantages and disadvantages of using
PrintWriter objects versus using SimpleOutput
objects?
Step 6.
Create an ArrayPrinter class with a main method
that contains the following lines:
int[] stuff = new int[4];
stuff[0] = 1;
stuff[1] = 2;
stuff[2] = 4;
out.println(stuff);
Try to compile and execute the ArrayPrinter. If you succeed,
record the output. If your fail, record and explain the error messages.
Step 1.
Create a new class, MyOutput, that contains
writer, of class OutputWriter;
println(String) method.
Step 2.
Create a new class, OutputTester, that will be used to
test your MyOutput class. Your class should contain
a main method with the following lines.
MyOutput out = new MyOutput();
out.println("Hello");
Compile your two classes and execute OutputTester. Record
the results.
Step 3. Add the line
out.println(7.2);
to the main method in OutputTester. Try to
compile and execute the program. Record any error messages you receive.
Step 4. What do those messages suggest?
Step 5.
Add a println(double) method to
MyOutput and recompile both classes. Do you still
receive the same error messages? If so, why? If not, why not?
Step 6. Add the line
out.println(6);
to the main method in OutputTester.
What do you expect to happen when you compile and execute your program?
Step 7. Try to compile and execute the program. If compilation and execution succeed, record the output. If either fails, record the error messages.
Step 8. Did what happened in step 7 correspond to what you suggested in step 6? Explain what happened in step 7.
Step 9.
What do you expect would happen if we added the following lines to
the main method in OutputTester?
int[] stuff = new int[4];
stuff[0] = 1;
stuff[1] = 2;
stuff[2] = 4;
out.println(stuff);
Step 1.
Add the following constructor to your MyOutput class.
Note that you will need to import java.io.FileWriter.
/**
* 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
Try to compile MyOutput and record the results.
When you are done, read the notes on this experiment.
Step 1.
Update the MyOutput(String) constructor from the
previous experiment to use
try and catch.
The body of the constructor will now be
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) {
// Do nothing
} // catch(Exception)
Try to compile the revised MyOutput and record the results.
Step 2.
Add the following lines to the main routine of your
OutputTester class. Recompile and execute
OutputTester. What are the contents of the file
stuff.txt?
// Write some information to a file.
file = new MyOutput("stuff.txt");
file.println("Hello");
file.println(2 + 3);
Step 3.
Change the file name in the code from step 2 from
stuff.txt to the empty string or to another illegal
filename specified by your instructor. Recompile and execute
OutputTester. What happens?
Step 4. Add the line
throw new Exception("Cannot write to '" + file + "'");
to the MyOutput(String) constructor. Recompile MyOutput.
If you are successful, execute OutputTester. Record either results
or error messages.
Step 5.
Change the method header for the MyOutput(String constructor
to read
public MyOutput(String filename) throws Exception {
Recompile MyOutput and TestOutput. If you are
successful, execute OutputTester and record the results. If
you are not successful, record the error messages.
Step 6.
Update the main in TestOutput to use
appropriate try/catch clauses. Summarize your changes.
Step 1.
Create a new class, MyInput, that contains
reader, of class BufferedReader;
String readString() method using the body we
developed earlier.
Here is the constructor we created earlier.
/**
* 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
Here is the body of the readString() method we developed earlier.
return reader.readLine();
Compile your class. What error messages do you receive? What do these error messages suggest?
After answering this question, you may want to look at the notes on this step.
Step 2.
Update readString so that it uses
try and catch
appropriately. Return the empty string if you catch an exception.
Compile your class and correct any compilation errors. Record the code
for your method here.
Step 3.
Create a new class, InputTester, that will be used to
test your MyOutput class. Your class should contain
a main method with the following lines.
SimpleOutput out = new SimpleOutput();
MyInput in = new MyInput();
out.println("Enter your name: ");
String str = in.readString();
out.println("You entered " + str);
Compile your two classes and execute InputTester. Enter
your name when prompted to do so. Record the results.
Step 4.
How would this code differ if you used SimpleInput
rather than MyInput?
After writing your answer, you may wish to look at the notes on this step.
Step 5.
Update InputTester to read two lines of text, such as
a name and a major. Recompile and execute the program. Summarize your
changes and record the output.
Step 6. Add the lines
int i; i = in.readInt();
to the main method in InputTester. Try to
compile and execute the program. Record any error messages you receive.
What do these messages suggest?
Step 7.
Try to update MyInput to include a readInt
method that calls a corresponding method in its base reader. Try to
compile the updated class. What do the error messages you receive
suggest?
Step 1.
Add one of the readInt methods described above to your
MyInput class. Recompile the class and correct any
compilation errors. Here is one possible definition of
readInt.
/**
* 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()
Step 2.
Add the following lines to the main method of
InputTester.
int i;
out.println("Enter an integer");
i = in.readInt();
out.println("Read " + i);
Recompile and execute InputTester.
Enter the value 3. Record your results. What do those results suggest?
Step 3.
Rerun InputTester using the following inputs
-1
004
3.5
three
10 20
98765432109876543210
Record the results for each case. What do these results suggest?
Step 4.
Add a readFloat method to your MyInput
class. The design of this method is left to your imagination. Enter
the code for the method here.
Step 5.
Update InputTester to read in and print out a
float. Run your program with the following inputs.
-1
004
3.5
three
10 20
98765432109876543210
Record the results for each case. What do these results suggest?
Add a println(int[] ints) method to
the MyOutput class. This method should print
the array nicely. For example, the code
SimpleOutput out = new SimpleOutput();
int[] stuff = new int[4];
stuff[0] = 1;
stuff[1] = 2;
stuff[2] = 4;
out.println(stuff);
should print
[1, 2, 4, 0]
Add a println(String[] strings) method to
the MyOutput class. This method should print
the array nicely.
Add a setOutput(String filename) method to your
MyOutput class that redirects output to a
file. If the object was previously writing to standard output,
it should now write to the given file. If the object was previously
writing to another file, it should close that file and begin writing
to a new file.
Add a setOutput() method to your MyOutput
class that allows one to stop writing to whatever was previously
being written to and then begin writing to standard output. if
the object was previously writing to a file, it should close that file.
Update MyInput so that it can also read input from files.
As in the case of MyOutput, you will need to add a
constructor that takes a file name as a parameter. You will turn that
file name into a FileReader and then into a
BufferedReader.
You may have noted that one significant difference between
MyInput and SimpleInput is that
SimpleInput objects allow you to read multiple numeric
values from one line. Update MyInput to support these
additional methods.
How will you do this? You'll need to keep track of the line most recently read. You can identify the positions of the spaces on that line, split the line at each space, and convert the substrings individually.
Add setInput methods to MyInput that behave
similarly to the setOutput methods described in previous
problems.
Experiment X3.1, Step 3.
Why did we provide a
SimpleOutput class? Primarily because it seemed like a
good way to introduce objects. Note that the first program we gave you
used a SimpleOutput object. In addition, as you will see
later, by using SimpleOutput objects, we can easily change
our programs to write to files rather than to the screen. It is also
possible to extend the SimpleOutput object to provide
additional capabilities. For example, we can add a method to print
arrays. Finally, by relying on a single class that we developed, we can
easily update all of our programs when Java incorporates a new output
mechanism.
Experiment X3.3, Step 1.
It is likely that the compilation will fail. If your error message
indicates that it did not understand about the FileReader,
then you forgot to import that class. Once you've fixed that error,
you will now receive an error about an exception that must be caught.
We discuss this error in the section titled ``Exceptions''.
Experiment X3.5, Step 2.
It turns out that readString throws an exception.
Exceptions are typically caused
when you attempt to read from beyond the end of input.
Hence, we need to try the
call to readString and catch
an exception.
Experiment X3.5, Step 4.
As long as the program only reads and writes lines of text, there should
be no difference between a program that uses our newly developed classes
and one that uses SimpleInput. On the other hand, since
we have not yet written methods to permit reading of numeric values,
there will be some differences.
[Front Door] [Introduction] [Code]
Copyright (c) 1998 Samuel A. Rebelsky. All rights reserved.
Source text last modified Wed Oct 6 12:42:00 1999.
This page generated on Tue Oct 26 15:38:49 1999 by Siteweaver.
Contact our webmaster at rebelsky@math.grin.edu