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 ) ); if ( args.length > 1 ) outputFile = new PrintWriter( new FileWriter( args ) );
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!
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.
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
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.
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.