Write a quick-and-dirty main program named CountCharacters that displays the number of characters typed on the command line after the command, not counting the spaces.
For example:
mac os x > java CountCharacter The command line contains 0 characters. mac os x > java CountCharacters a The command line contains 1 character. mac os x > java CountCharacters Eugene is a great guy! The command line contains 18 characters.
Here is a simple job:
public class CountCharacters { public static void main( String[] args ) { int characterCount = 0 ; for ( int i = 0; i < args.length; i++ ) characterCount += args[i].length(); String suffix = (characterCount == 1) ? "" : "s"; System.out.println( "The command line contains " + characterCount + " character" + suffix + "." ); } }
As noted last time, the java.awt package is chock-full of all kinds of graphical goodies. The capabilities of many of these classes (say, the text editing capabilities of TextArea provided by the default TextListener) are quite handy. They take a lot of burden off of the programmer to write code for common, low-level tasks.
The java.awt is also chock-full of examples showing how polymorphism can simplify interaction among objects and remove unnecessary details from the programmer's mind. For example, in Session 22, we saw how Panels can hold graphical Components for placement in a Frame. But a Panel is itself a Component, which means that Panels can be nested. And Panels and Frames both have layout managers that work in the same way, so programmers can organize components in the same way without worrying about which they are using.
Talking about polymorphism is not enough for us to understand what it is or how to use it, and talking about polymorphism won't help us learn Java much at all. Programming with these ideas will help you learn Java and help you understand the benefits of polymorphism.
This week, we study more of teh java.io package. In particular, we learn what we need to know to understand a piece of "magic" that we have been trusting since Session 10:
BufferedReader inputFile = new BufferedReader( new FileReader( args[0] ) );
It turns out that we already know what we need to know -- we just don't know that we know. :-)
Up until now, we have worked with readers and writers when doing input and output, respectively. Readers and writers are objects that know specifically how to work with text. But they are built out of objects that know how to work with any kind of characters.
We will now consider, these more basic objects, Java's stream classes. Our goal is two-fold:
That's how the world works sometimes.
A stream is a sequence of data. Many modern languages define their input and output (I/O) facilities in terms of input streams and output streams.
An input stream serves as the source of data for a program. For now, we will focus our study on input streams, but they work in much the same way as output streams.
Consider how a stream works:
Characters flow by, one at a time, and the program can access them in just this order. Once a character flows by, the program cannot access it again.
Java provides a number of different input streams, all of which extend the abstract class InputStream:
public abstract class InputStream { public abstract int read() throws IOException // Subclasses must implement this method. public int read( byte[] b ) throws IOException { return read( b, 0, b.length); } public int read( byte[] b, int offset, int length ) throws IOException { // Uses a loop to read a single byte at a time // into b, using read(). Returns the number of // bytes read into the buffer, or -1. } public long skip( long n ) throws IOException { // The receiver discards n bytes. May skip fewer // bytes, even 0. Returns the actual number skipped. } public int available() throws IOException { // Subclasses should override this method. return 0; } public void close() throws IOException {} // ... a set of methods for marking and using // a spot in the stream }
Notice some of the interesting features of this class:
But the more complex read methods are all implemented in terms of the basic method. So we can create subclasses of InputStream simply by overriding read().
Such a method returns the characters it reads by modifying one of its arguments, and returns the status of the operation as its Java return value.
We can implement the skip( long n ) method in terms of read().
How?
The basic loop would have to look something like this:
for (int i = 0; i < n; i++) read();
But what happens if the stream contains fewer than n characters? The read() method throws an exception. Fortunately, that is easy to fix:
public long skip( long n ) { int numberToRead = Math.min( n, available() ); for (int i = 0; i < numberToRead; i++) read(); return numberToRead; }
skip() uses the object's read() and available() methods to implement its behavior. The more complicated read() methods use the object's basic read() to implement their behavior.
What is the value in writing most or all of the methods in InputStream in terms of read()?
This sort of abstract design means that only a few of the class's methods need to know about the class's instance variables.
Designing classes in this way makes them easier to use, extend, and modify. To make a new kind of InputStream, you really only have to implement a read() method and an available() method!
InputStream is a great design example for us. It shows us that we can implement abstract classes that have no data at all -- yet still implement useful behaviors for future clients!
A physical input stream is an input stream that reads directly from a real, actual, physical source in the machine or in a program.
Java provides five physical input stream classes. All of these classes override appropriate methods inherited from InputStream.
In particular, each defines read() to return a byte [an int], the ASCII value for a character. This value must be cast as a char before using it as a character.
The five physical input streams defined in java.awt are:
(The javax.sound.sampled package defines a sixth physical input stream, AudioInputStream, for working with bytes of audio data accessed from a file, stream or URL.)
You have some experience opening FileInputStreams, but not using them directly.
An instance of this class reads from a file on disk, accessed through its file name (a String).
Consider this simple example that counts all of the characters in a file.
mac os x > java CountBytes0 CountBytes0.java 425 bytes mac os x > wc CountBytes0.java 17 46 425 CountBytes0.java
Because we don't need the characters we read, we just need to check that a read gives us a character at all. Thus, we can use dataSource.read() != -1 as our loop control.
Modify CountBytes0 so that it reads from standard input whenever the user does not specify a filename.
All we need to do is change the line that initializes the dataSource variable:
InputStream dataSource; if ( args.length == 0 ) dataSource = System.in; else dataSource = new FileInputStream( args[0] );
Everything else stays the same. See this solution.
It's that simple,
with polymorphic variables
and substitutable classes.
An instance of this class reads from a String as if it were a file!
Huh? Remember the definition of an input stream, given above: a sequence of characters that serves as the source of data for a program. A String is a sequence of characters, and a StringBufferInputStream lets us "read" from a String.
Consider this simple example that counts the characters in a string.
mac os x > javac CountBytes2.java Note: CountBytes2.java uses or overrides a deprecated API. Note: Recompile with -deprecation for details. mac os x > java CountBytes2 CountBytes2.java 16 bytes
Nothing else changes from CountBytes0 or CountBytes1, just the value we store in the dataSource variable. That, my friends, is polymorphism at work.
The only problem with CountBytes0, CountBytes1, and CountBytes2 is that they duplicate the processing loop. We'd like to isolate repeated code at a single place in our program. And main() methods that get very big at all almost always mask an object that we' like to use. So let's factor out a class that counts characters in an InputStream:
public class StreamAnalysis { private InputStream dataSource; public StreamAnalysis( InputStream in ) { dataSource = in; } public int charCount() throws IOException { int totalCharacters = 0; while ( dataSource.read() != -1 ) totalCharacters++; return totalCharacters; } }
Now, our CountBytesX programs just need to choose their streams and hand them to a StreamAnalysis object. See, for example, CountBytes2.
Suppose that we wanted to modify CountBytes2 so that it could accept any number of command-line arguments.
What should we do?
We could "do it by hand" and write something like this:
public static void main( String [] args ) throws IOException { InputStream dataSource; int totalCharacters = 0; for ( int i = 0; i < args.length; i++ ) { dataSource = new StreamAnalysis( new StringBufferInputStream(args[i]) ); totalCharacters += dataSource.charCount(); } System.out.println( totalCharacters + " bytes" ); }
But this requires us to write our own counting code: create a variable, initialize it, and increment it. StreamAnalysis objects already do that for us.
Even worse: what about the option to read from standard input? Do we need to make a special case to handle stdin separately from the String arguments we might receive?
The sky is dark...
Java offers us a better solution: a composite stream. Do you remember the Composite Pattern, which we learned about in Session 22? A Panel holds Components but is itself a Component, which allows us to nest panels.
The SequenceInputStream class is a composite: it holds multiple InputStreams and treats them as an uninterrupted sequence of characters. But it, too, is an InputStream, so we can use it in any code that expects an InputStream.
CountBytes3b.java demonstrates how to use a SequenceInputStream. The only part we don't know yet is the elements() being sent to the Vector of StringBufferInputStreams. It turns out that SequenceInputStream's constructor won't take a Vector as an argument, but it will take a listing of a Vector's elements. We'll learn about this bit of Java next week.
But does it work?
mac os x > java CountBytes3b Eugene is a great guy, and SequenceInputStreams are cool. 49 bytes mac os x > java CountBytes3a Eugene is a great guy, and SequenceInputStreams are cool. 49 bytes
It does! More of the power of polymorphism...