Session 11

Doing More with Java I/O


810:062
Computer Science II
Object-Oriented Programming


What Did We Learn Last Time?


Working with Standard Input and Output as Files

Sometimes, we'd like to give the user an option of providing a file name or using standard I/O. Consider our examples from last session. We can call Unix's sort command with its own file argument, or we can pipe the standard output of one program (say, cat hamlet.out) as the standard input to sort.

How can we make our Java programs do the same thing?

Consider Echo.java from last time. The critical points in the code are these two lines:

    buffer = inputFile.readLine();
    ...
    outputFile.println( word );

Why are they critical? These are the only places in our processing code that interact with the files. So, if we want to use standard input in place of the input file, we need a way to change the former statement; if we want to use standard output in place of the output file, then we need a way to change the latter statement.

In Java, standard input is an instance of the InputStream class. InputStreams do not respond to readLine(), which is how we would like to grab lines of text conveniently. Standard output does respond to println() messages, but it is a PrintStream, which cannot be stored in a PrintWriter variable. This all seems unnecessarily complicated. What can we do?

We could write duplicate code for the different cases. But how many are there? There are four, and they'd all look the same except for one or two lines. That doesn't seem to be the best solution. It duplicates a lof code.

... Talked some about how the BufferedReader and PrintWriter objects rely on other objects to help them, but that they don't know how to talk directly to standard input and output. But they are the objects that *we* want to work with in our main() method, so maybe we can find a way to have them talk to objects that talk to standard input and output. ...

Instead, let's take advantage of an object-oriented idea: We ought to be able to substitute an object with a common interface, even if different behavior, in place of one another, and let the new object fulfill the responsibilities of the replaced one.

While BufferedReaders and PrintWriters don't know how to talk to standard input and output, respectively, we can use a translator to serve as a go-between. Java give us the classes we need: InputStreamReader and OutputStreamWriter.

Take a look at Echo.java, an improved version of Echo. The new class does just the same job as our original Echo, but it allows the user to work with standard input and standard output as well as input and output files. The only changes to this file from the original are in these four set-up lines:

    BufferedReader inputFile = new BufferedReader(
                                   new InputStreamReader( System.in ) );
    PrintWriter   outputFile = new PrintWriter(
                                   new OutputStreamWriter( System.out ) );

    if ( args.length > 0 )
        inputFile = new BufferedReader( new FileReader( args[0] ) );
    if ( args.length > 1 )
        outputFile = new PrintWriter( new FileWriter( args[1] ) );

By default, the program reads from standard input and writes to standard output. If the user gives one command-line argument, it is the name of an input file. If the user gives one command-line argument, it is the name of an input file. Here are some example of how the new code works:

    mac os x > java Echo
    foo bar baz big
    Eugene Wallingford teaches this course.
    foo
    bar
    baz
    big
    eugene
    wallingford
    teaches
    this
    course

    mac os x > java Echo ../support/hamlet.txt | less
    1604
    the
    tragedy
    of
    hamlet
    prince
    of
    denmark
    by
    william
    shakespeare
    ...

    mac os x > java Echo ../support/hamlet.txt hamlet.out
    mac os x  > less hamlet.out
    1604
    the
    tragedy
    of
    hamlet
    prince
    of
    denmark
    by
    william
    shakespeare
    ...

    mac os x > cat ../support/hamlet.txt | java EchoV2 | less
    1604
    the
    tragedy
    of
    ...

Whenever you need to read from standard input instead of a file, or write to standard output instead of a file, you can use objects created in this way. For now, don't worry about any details of InputStreamReader and OutputStreamWriter, or of BufferedReader and PrintWriter for that matter. We'll come back to these ideas later in the semester. But you can use these little snippets of code to write code that does input and output in flexible ways.

Notice: the processing code in this program stays exactly the same. This demonstrates a wonderful degree of "say what you mean" and "say it once and only once". Objects give us the power to do this.

Expound with great fervor.

Closing comments: That is a busy main() method. I'm already eager to find an object in the mess and factor it out of this code into a class, so that I could reuse it in different contexts. We're well on our way. We've managed to separate the creation of the input/output objects from the code that uses them to process a sequence of lines and words. Soon!


What About Today?

Let's use what we learned last time to build a fun little program, as a way to become more familiar with Java I/O and to learn to think about problems.


Exercise 1: Here's Your Sign

Write a class named Sign, whose main() method reads lines of text from standard input. For each line of input, Sign writes to standard output a line of the form

signature original

where original is the original line and signature is a string containing all the characters in original, sorted in alphabetical order. For example:

    |mac os x > java Sign > test-sign.out
    |hello eugene
    |I am sam

    |mac os x > less test-sign.out
    | eeeeghllnou hello eugene
    |  aaimms i am sam

    |mac os x > cat ../support/hamlet.txt | java Sign > test-sign.out
    |mac os x > less test-sign.out
    |0146 1604
    | 
    | 
    |      ,aaacddeeeeeffghhiklmmnnooprrrttty the tragedy of hamlet, prince of denmark
    | 
    | 
    |  aaabeeehiikllmprsswy by william shakespeare
    |[...]
    | 
    |     ,.aacddefgiikklmnnorsuu   claudius, king of denmark.
    |   ,.acceeffillmorrsu   marcellus, officer.
    |[...]

Notice that the signature is case-insensitive, and that spaces and punctuation count as parst of the tokens.

In Java, Strings are immutable -- that is, you can't change them. To get a String of sorted characters, you'll need to work with a char[]. You can use these methods to help you write your solution:

Here's my Sign. Factoring out the helper program made my job a lot easier. The two parts were easier to solve separately than together! You will learn more about sorting in Computer Science III.


Exercise 2: Squish Me

Write a class named Squish, whose main() method reads lines of text from standard input. Squish looks at the first token on each line. If the it is different from the first token on the previous line, then it writes a line containing ----- to standard output. In either case, it writes the second token from the current line to standard output. For example:

    mac os x > java Squish > test-squish.out
    hello cosmo
    hello jerry
    hello elaine 
    hello george
    are we there yet
    I don't think so
    jerry says
    jerry does
    jerry leaves
    cosmo watches
    george complains
    george leaves
    
    mac os x > less test-squish.out
    -----
    cosmo
    jerry
    elaine
    george
    -----
    we
    -----
    don't
    -----
    says
    does
    leaves
    -----
    watches
    -----
    complains
    leaves

Notice that Squish breaks only at spaces. Punctuation counts in its tokens.

Here's my version of Squish. This is mostly a matter of tracking the change in the signature.


What Have We Learned?


Wrap Up


Eugene Wallingford ..... wallingf@cs.uni.edu ..... February 15, 2005