Building the Right Environment to Support AI, Machine Learning and Deep Learning
|Bruce Eckel's Thinking in Java||Contents | Prev | Next|
and useful interfaces
The use of layered objects to dynamically and transparently add responsibilities to individual objects is referred to as the decorator pattern. (Patterns  are the subject of Chapter 16.) The decorator pattern specifies that all objects that wrap around your initial object have the same interface, to make the use of the decorators transparent – you send the same message to an object whether it’s been decorated or not. This is the reason for the existence of the “filter” classes in the Java IO library: the abstract “filter” class is the base class for all the decorators. (A decorator must have the same interface as the object it decorates, but the decorator can also extend the interface, which occurs in several of the “filter” classes).
Decorators are often used when subclassing requires a large number of subclasses to support every possible combination needed – so many that subclassing becomes impractical. The Java IO library requires many different combinations of features which is why the decorator pattern is a good approach. There is a drawback to the decorator pattern, however. Decorators give you much more flexibility while you’re writing a program (since you can easily mix and match attributes), but they add complexity to your code. The reason that the Java IO library is awkward to use is that you must create many classes – the “core” IO type plus all the decorators – in order to get the single IO object that you want.
The classes that provide the decorator interface to control a particular InputStream or OutputStream are the FilterInputStream and FilterOutputStream – which don’t have very intuitive names. They are derived, respectively, from InputStream and OutputStream, and they are abstract classes, in theory to provide a common interface for all the different ways you want to talk to a stream. In fact, FilterInputStream and FilterOutputStream simply mimic their base classes, which is the key requirement of the decorator.
Reading from an InputStream
The FilterInputStream classes accomplish two significantly different things. DataInputStream allows you to read different types of primitive data as well as String objects. (All the methods start with “read,” such as readByte( ), readFloat( ), etc.) This, along with its companion DataOutputStream, allows you to move primitive data from one place to another via a stream. These “places” are determined by the classes in Table 10-1. If you’re reading data in blocks and parsing it yourself, you won’t need DataInputStream, but in most other cases you will want to use it to automatically format the data you read.
The remaining classes modify the way an InputStream behaves internally: whether it’s buffered or unbuffered, if it keeps track of the lines it’s reading (allowing you to ask for line numbers or set the line number), and whether you can push back a single character. The last two classes look a lot like support for building a compiler (that is, they were added to support the construction of the Java compiler), so you probably won’t use them in general programming.
You’ll probably need to buffer your input almost every time, regardless of the IO device you’re connecting to, so it would have made more sense for the IO library to make a special case for unbuffered input rather than buffered input.
Table 10-3. Types of FilterInputStream
Writing to an OutputStream
The complement to DataInputStream is DataOutputStream, which formats each of the primitive types and String objects onto a stream in such a way that any DataInputStream, on any machine, can read them. All the methods start with “write,” such as writeByte( ), writeFloat( ), etc.
If you want to do true formatted output, for example, to the console, use a PrintStream. This is the endpoint that allows you to print all of the primitive data types and String objects in a viewable format as opposed to DataOutputStream, whose goal is to put them on a stream in a way that DataInputStream can portably reconstruct them. The System.out static object is a PrintStream.
The two important methods in PrintStream are print( ) and println( ), which are overloaded to print out all the various types. The difference between print( ) and println( ) is that the latter adds a newline when it’s done.
BufferedOutputStream is a modifier and tells the stream to use buffering so you don’t get a physical write every time you write to the stream. You’ll probably always want to use this with files, and possibly console IO.
Table 10-4. Types of FilterOutputStream
 In Design Patterns , Erich Gamma et al. , Addison-Wesley 1995. Described later in this book.