Streams and File I/O in Java Eric Allen Rice University Streams and Lazy Evaluation • Java I/O is based on the notion of streams • Streams are sequences of data (whose elements may be computed on demand) • Streams originated from functional programming, as an alternative to mutation A Very Simple Stream class OneStream extends IntStream { public int next() { return 1; } } A Slightly More Complex Stream public class NatStream { private int current = 0; public int next() { return current++; } } Streams And Computation • Streams can be composed and filtered to produce more complex streams • Let’s apply the Union, Composite, and Command Patterns to construct composable streams of ints… IntStream NatStream ComposableIntStream abstract class IntStream { public abstract int next(); } class NatStream extends IntStream { private int current = 0; public int next() { return current++; } } abstract class ComposableIntStream extends IntStream { private Operator op; private IntStream left; private IntStream right; public ComposableIntStream(Operator _op, IntStream _left, IntStream _right) { op = _op; left = _left; right = _right; } public int next() { return op.apply(left.next(), right.next()); } } abstract class Operator { public abstract int apply(int left, int right); } // for example, class Adder extends Operator { public int apply(int left, int right) { return left + right; } } // and class Multiplier extends Operator { public int apply(int left, int right) { return left * right; } } Now we can construct all sorts of nifty composable IntStreams: class EvenStream extends ComposableIntStream { public EvenStream() { super(new Adder(), new NatStream(), new NatStream()); } } class SquareStream extends ComposableIntStream { public SquareStream() { super(new Multiplier(), new NatStream(), new NatStream()); } } Building the Natural Numbers class ZeroStream extends IntStream { public int next() { return 0; } } class AdderStream extends ComposableIntStream { public AdderStream(IntStream left, IntStream right) { super(new Adder(), left, right); } } Building the Natural Numbers class AlternateNatStream extends IntStream { IntStream value = new ZeroStream(); public int next() { value = new AdderStream(new OneStream(), value); return value.next(); } } In fact, streams can be used as a foundation for all of number theory! • Exercise (optional/not for credit): Extend IntStreams to include a stream of prime numbers • Hint: define the notion of filtered streams, as a subtype of ComposableIntStreams Applications of Streams • Streams are natural models of many realworld systems: – – – – Stock prices Mouse/keyboard/monitor input Radio signals Human input to a program (DrJava interactions) – Contents of a file Output Streams Model Systems That Take Input • Just as we can read from sources, we can write to destinations • Output streams take input as it’s computed I/O Streams in Java • java.io.InputStream, java.io.OutputStream • Readers, writers are adapters to streams, to make certain sorts of I/O easier Reading from a File > FileReader fReader = new FileReader(fn); > fReader.read() 97 > (char)fReader.read() b Writing to a File > FileWriter fWriter = new FileWriter(fn); > fWriter.write() Testing Writers • Readers/Writers can be composed using PipedReaders and PipedWriters PipedReader pReader = new PipedReader(); PipedWriter pWriter = new PipedWriter(pReader); Testing Writers • By always writing to a Writer field (as opposed to hard-wired System.out, etc.), you can test your classes more easily • Pass in PipedWriters in your test cases and check what’s sent to a corresponding PipedReader Stream Tokenization • Often we want to view elements of a stream as structures larger than the elements themselves Stream Tokenization • Consider the syntactic components of a Java program: keywords, vars, etc. class C extends D implements E {…} • These elements are more complex than just characters Stream Tokenization In such cases, we can place a Façade stream over the original s.t. the elements of the Façade are sequences of the original elements Stream Tokenizer • Java provides a built-in StreamTokenizer class • Warning: The design of this class is ugly • Always put an Adapter class over it first (or write your own!) • This class is very powerful, but it’s biased toward parsing Java code Using StreamTokenizer > Reader r = new BufferedReader(new InputStreamReader(si)); > StreamTokenizer tokenizer = new StreamTokenizer(r); > tokenizer.nextToken() 42