Adding attributes and useful interfaces

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
[47]
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

with
FilterInputStream

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

Class

Function

Constructor
Arguments

How
to use it

Data-InputStream

Used
in concert with
DataOutputStream,
so you can read primitives (int, char, long, etc.) from a stream in a portable
fashion.

InputStream

Contains
a full interface to allow you to read primitive types.

Buffered-InputStream

Use
this to prevent a physical read every time you want more data. You’re
saying “Use a buffer.”

InputStream,
with optional buffer size.

This
doesn’t provide an interface
per
se
,
just a requirement that a buffer be used. Attach an interface object.

LineNumber-InputStream

Keeps
track of line numbers in the input stream; you can call
getLineNumber( )
and
setLineNumber(int).

InputStream

This
just adds line numbering, so you’ll probably attach an interface object.

Pushback-InputStream

Has
a one byte push-back buffer so that you can push back the last character read.

InputStream

Generally
used in the scanner for a compiler and probably included because the Java
compiler needed it. You probably won’t use this.

Writing
to an OutputStream

with
FilterOutputStream

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

Class

Function

Constructor
Arguments

How
to use it

Data-OutputStream

Used
in concert with
DataInputStream
so you can write primitives (int, char, long, etc.) to a stream in a portable
fashion.

OutputStream

Contains
full interface to allow you to write primitive types.

PrintStream

For
producing formatted output. While
DataOutputStream
handles the
storage
of data,
PrintStream
handles
display.

OutputStream,
with optional boolean indicating that the buffer is flushed with every newline.

Should
be the “final” wrapping for your
OutputStream
object. You’ll probably use this a lot.

Buffered-OutputStream

Use
this to prevent a physical write every time you send a piece of data.
You’re saying “Use a buffer.” You can call
flush( )
to flush the buffer.

OutputStream,
with optional buffer size.

This
doesn’t provide an interface
per
se
,
just a requirement that a buffer is used. Attach an interface object.


[47]
In
Design
Patterns
,
Erich Gamma
et
al.
,
Addison-Wesley 1995. Described later in this book.

More by Author

Must Read