|
|||||
been
a kind of group process and
it has really made the
book into
something
special.
But
then I started hearing "OK,
fine, it's nice you've
put up an electronic
version,
but I want a printed and
bound copy from a real
publisher." I
tried
very hard to make it easy
for everyone to print it out
in a nice looking
format
but that didn't stem
the demand for the
published book. Most
people
don't want to read the
entire book on screen, and
hauling around a
sheaf
of papers, no matter how
nicely printed, didn't
appeal to them
either.
(Plus, I think it's not so
cheap in terms of laser
printer toner.) It
seems
that the computer revolution
won't put publishers out of
business,
after
all. However, one student
suggested this may become a
model for
future
publishing: books will be
published on the Web first,
and only if
sufficient
interest warrants it will
the book be put on paper.
Currently, the
great
majority of all books are
financial failures, and
perhaps this new
approach
could make the publishing
industry more
profitable.
This
book became an enlightening
experience for me in another
way. I
originally
approached Java as "just
another programming
language,"
which
in many senses it is. But as
time passed and I studied it
more
deeply,
I began to see that the
fundamental intention of this
language is
different
from all the other
languages I have
seen.
Programming
is about managing complexity:
the complexity of the
problem
you want to solve, laid
upon the complexity of the
machine in
which
it is solved. Because of this
complexity, most of our
programming
projects
fail. And yet, of all
the programming languages of
which I am
aware,
none of them have gone
all-out and decided that
their main design
goal
would be to conquer the
complexity of developing and
maintaining
programs1. Of
course, many language design
decisions were made
with
complexity
in mind, but at some point
there were always some
other
issues
that were considered
essential to be added into
the mix. Inevitably,
those
other issues are what
cause programmers to eventually
"hit the
wall"
with that language. For
example, C++ had to be
backwards-
compatible
with C (to allow easy
migration for C programmers), as
well as
1
I take this back on the
2nd edition: I believe that the
Python language comes closest
to
doing
exactly that. See
www.Python.org.
2
Thinking
in Java
efficient.
Those are both very
useful goals and account
for much of the
success
of C++, but they also
expose extra complexity that
prevents some
projects
from being finished
(certainly, you can blame
programmers and
management,
but if a language can help
by catching your mistakes,
why
shouldn't
it?). As another example,
Visual Basic (VB) was
tied to BASIC,
which
wasn't really designed to be an
extensible language, so all
the
extensions
piled upon VB have produced
some truly horrible
and
unmaintainable
syntax. Perl is backwards-compatible
with Awk, Sed,
Grep,
and other Unix tools it
was meant to replace, and as
a result is often
accused
of producing "write-only code"
(that is, after a few
months you
can't
read it). On the other
hand, C++, VB, Perl,
and other languages
like
Smalltalk
had some of their design
efforts focused on the issue
of
complexity
and as a result are
remarkably successful in solving
certain
types
of problems.
What
has impressed me most as I
have come to understand Java
is what
seems
like an unflinching goal of
reducing complexity for
the
programmer.
As if to say "we don't care
about anything except
reducing
the
time and difficulty of
producing robust code." In
the early days,
this
goal
has resulted in code that
doesn't run very fast
(although there have
been
many promises made about
how quickly Java will
someday run) but
it
has indeed produced amazing
reductions in development time;
half or
less
of the time that it takes to
create an equivalent C++
program. This
result
alone can save incredible
amounts of time and money,
but Java
doesn't
stop there. It goes on to
wrap all the complex
tasks that have
become
important, such as multithreading
and network programming,
in
language
features or libraries that
can at times make those
tasks trivial.
And
finally, it tackles some
really big complexity
problems: cross-platform
programs,
dynamic code changes, and
even security, each of which
can fit
on
your complexity spectrum
anywhere from "impediment" to
"show-
stopper."
So despite the performance
problems we've seen, the
promise of
Java
is tremendous: it can make us
significantly more
productive
programmers.
One
of the places I see the
greatest impact for this is
on the Web. Network
programming
has always been hard,
and Java makes it easy
(and the Java
language
designers are working on
making it even easier).
Network
programming
is how we talk to each other
more effectively and
cheaper
than
we ever have with telephones
(email alone has
revolutionized many
Preface
3
businesses).
As we talk to each other
more, amazing things begin
to
happen,
possibly more amazing even
than the promise of
genetic
engineering.
In
all ways--creating the
programs, working in teams to
create the
programs,
building user interfaces so
the programs can
communicate
with
the user, running the
programs on different types of
machines, and
easily
writing programs that
communicate across the
Internet--Java
increases
the communication bandwidth
between
people. I think
that
perhaps
the results of the
communication revolution will
not be seen from
the
effects of moving large
quantities of bits around; we
shall see the
true
revolution
because we will all be able
to talk to each other more
easily:
one-on-one,
but also in groups and, as a
planet. I've heard it
suggested
that
the next revolution is the
formation of a kind of global
mind that
results
from enough people and
enough interconnectedness. Java
may or
may
not be the tool that
foments that revolution, but
at least the
possibility
has made me feel like
I'm doing something
meaningful by
attempting
to teach the
language.
nd
Preface
to the 2
edition
People
have made many, many
wonderful comments about the
first
edition
of this book, which has
naturally been very pleasant
for me.
However,
every now and then
someone will have
complaints, and for
some
reason one complaint that
comes up periodically is "the
book is too
big."
In my mind it is faint damnation
indeed if "too many pages"
is your
only
complaint. (One is reminded of
the Emperor of Austria's
complaint
about
Mozart's work: "Too many
notes!" Not that I am in any
way trying
to
compare myself to Mozart.) In
addition, I can only assume
that such a
complaint
comes from someone who is
yet to be acquainted with
the
vastness
of the Java language itself,
and has not seen
the rest of the
books
on
the subject--for example, my
favorite reference is Cay
Horstmann &
Gary
Cornell's Core
Java (Prentice-Hall),
which grew so big it had to
be
broken
into two volumes. Despite
this, one of the things I
have attempted
to
do in this edition is trim
out the portions that
have become obsolete,
or
at
least nonessential. I feel
comfortable doing this
because the original
material
remains on the Web site
and the CD ROM that
accompanies this
book,
in the form of the
freely-downloadable first edition of
the book (at
4
Thinking
in Java
).
If you want the old
stuff, it's still there,
and this is
a
wonderful relief for an
author. For example, you
may notice that
the
original
last chapter, "Projects," is no
longer here; two of the
projects have
been
integrated into other
chapters, and the rest
were no longer
appropriate.
Also, the "Design Pattens"
chapter became too big
and has
been
moved into a book of its
own (also downloadable at
the Web site).
So,
by all rights the book
should be thinner.
But
alas, it is not to
be.
The
biggest issue is the
continuing development of the
Java language
itself,
and in particular the
expanding APIs that promise
to provide
standard
interfaces for just about
everything you'd like to do
(and I won't
be
surprised to see the
"JToaster" API eventually
appear). Covering all
these
APIs is obviously beyond the
scope of this book and is a
task
relegated
to other authors, but some
issues cannot be ignored.
The biggest
of
these include server-side
Java (primarily Servlets &
Java Server pages,
or
JSPs),
which is truly an excellent
solution to the World Wide
Web
problem,
wherein we've discovered
that the various Web
browser
platforms
are just not consistent
enough to support
client-side
programming.
In addition, there is the
whole problem of easily
creating
applications
to interact with databases,
transactions, security, and
the
like,
which is involved with
Enterprise Java Beans
(EJBs). These topics
are
wrapped into the chapter
formerly called "Network
Programming"
and
now called "Distributed
Computing," a subject that is
becoming
essential
to everyone. You'll also
find this chapter has
been expanded to
include
an overview of Jini (pronounced
"genie," and it isn't an
acronym,
just
a name), which is a cutting-edge
technology that allows us to
change
the
way we think about
interconnected applications. And of
course the
book
has been changed to use
the Swing GUI library
throughout. Again, if
you
want the old Java
1.0/1.1 stuff you can
get it from the
freely-
downloadable
book at
(it
is also included on
this
edition's
new CD ROM, bound into
the book; more on that a
little later).
Aside
from additional small
language features added in
Java 2 and
corrections
made throughout the book,
the other major change is in
the
collections
chapter (9), which now
focuses on the Java 2
collections used
throughout
the book. I've also
improved that chapter to
more deeply go
into
some of the important issues
of collections, in particular how a
hash
Preface
5
function
works (so that you
can know how to properly
create one). There
have
been other movements and
changes, including a rewrite of
Chapter
1,
and removal of some
appendices and other
material that I consider
no
longer
necessary for the printed
book, but those are
the bulk of them. In
general,
I've tried to go over
everything, remove from the
2nd edition what
is
no longer necessary (but
which still exists in the
electronic first
edition),
include
changes, and improve
everything I could. As the
language
continues
to change--albeit not quite at
the same breakneck pace
as
before--there
will no doubt be further
editions of this
book.
For
those of you who still
can't stand the size of
the book, I do
apologize.
Believe
it or not, I have worked
hard to keep it small.
Despite the bulk, I
feel
like there may be enough
alternatives to satisfy you.
For one thing,
the
book is available electronically
(from the Web site,
and also on the CD
ROM
that accompanies this book),
so if you carry your laptop
you can
carry
the book on that with no
extra weight. If you're
really into slimming
down,
there are actually Palm
Pilot versions of the book
floating around.
(One
person told me he would read
the book in bed on his
Palm with the
backlighting
on to keep from annoying his
wife. I can only hope
that it
helps
send him to slumberland.) If
you need it on paper, I know
of people
who
print a chapter at a time
and carry it in their
briefcase to read on
the
train.
Java
2
At
this writing, the release of
Sun's Java
Development Kit (JDK)
1.3 is
imminent,
and the proposed changes
for JDK 1.4 have
been publicized.
Although
these version numbers are
still in the "ones," the
standard way
to
refer to any version of the
language that is JDK 1.2 or
greater is to call it
"Java
2." This indicates the
very significant changes
between "old Java"--
which
had many warts that I
complained about in the
first edition of this
book--and
this more modern and
improved version of the
language,
which
has far fewer warts
and many additions and
nice designs.
This
book is written for Java 2.
I have the great luxury of
getting rid of all
the
old stuff and writing to
only the new, improved
language because the
old
information still exists in
the electronic 1st edition on the Web
and on
the
CD ROM (which is where you
can go if you're stuck using
a pre-Java-2
version
of the language). Also,
because anyone can freely
download the
6
Thinking
in Java
JDK
from java.sun.com, it means
that by writing to Java 2
I'm not
imposing
a financial hardship on someone by
forcing them to
upgrade.
There
is a bit of a catch, however.
JDK 1.3 has some
improvements that
I'd
really like to use, but
the version of Java that is
currently being
released
for Linux is JDK 1.2.2.
Linux (see www.Linux.org) is a
very
important
development in conjunction with
Java, because it is
fast
becoming
the most important server
platform out there--fast,
reliable,
robust,
secure, well-maintained, and
free, a true revolution in
the history
of
computing (I don't think
we've ever seen all of
those features in any
tool
before). And Java has
found a very important niche
in server-side
programming
in the form of Servlets,
a technology that is a
huge
improvement
over the traditional CGI
programming (this is covered
in
the
"Distributed Programming"
chapter).
So
although I would like to
only use the very
newest features, it's
critical
that
everything compiles under
Linux, and so when you
unpack the source
code
and compile it under that OS
(with the latest JDK)
you'll discover
that
everything will compile.
However, you will find
that I've put
notes
about
features in JDK 1.3 here
and there.
The
CD ROM
Another
bonus with this edition is
the CD ROM that is packaged
in the
back
of the book. I've resisted
putting CD ROMs in the back
of my books
in
the past because I felt
the extra charge for a
few Kbytes of source
code
on
this enormous CD was not
justified, preferring instead to
allow people
to
download such things from my
Web site. However, you'll
soon see that
this
CD ROM is different.
The
CD does contain the source
code from the book,
but it also contains
the
book in its entirety, in
several electronic formats. My
favorite of these
is
the HTML format, because it
is fast and fully
indexed--you just click
on
an
entry in the index or table
of contents and you're
immediately at that
portion
of the book.
The
bulk of the 300+ Megabytes
of the CD, however, is a
full multimedia
course
called Thinking
in C: Foundations for C++ & Java.
I
originally
commissioned
Chuck Allison to create this
seminar-on-CD ROM as a
Preface
7
stand-alone
product, but decided to
include it with the second
editions of
both
Thinking
in C++ and
Thinking
in Java because of
the consistent
experience
of having people come to
seminars without an
adequate
background
in C. The thinking apparently
goes "I'm a smart
programmer
and
I don't want
to
learn C, but rather C++ or
Java, so I'll just skip C
and
go
directly to C++/Java." After
arriving at the seminar, it
slowly dawns on
folks
that the prerequisite of
understanding C syntax is there
for a very
good
reason. By including the CD
ROM with the book, we
can ensure that
everyone
attends a seminar with
adequate preparation.
The
CD also allows the book to
appeal to a wider audience.
Even though
Chapter
3 (Controlling program flow)
does cover the fundamentals
of the
parts
of Java that come from C,
the CD is a gentler introduction,
and
assumes
even less about the
student's programming background
than
does
the book. It is my hope that
by including the CD more
people will be
able
to be brought into the fold
of Java programming.
8
Thinking
in Java
Introduction
Like
any human language, Java
provides a way to
express
concepts.
If successful, this medium of
expression will be
significantly
easier and more flexible
than the alternatives
as
problems grow larger and
more complex.
You
can't look at Java as just a
collection of features--some of the
features
make
no sense in isolation. You
can use the sum of
the parts only if
you
are
thinking about design,
not simply coding. And to
understand Java in
this
way, you must understand
the problems with it and
with
programming
in general. This book
discusses programming
problems,
why
they are problems, and
the approach Java has
taken to solve them.
Thus,
the set of features I
explain in each chapter are
based on the way I
see
a particular type of problem
being solved with the
language. In this
way
I hope to move you, a little
at a time, to the point
where the Java
mindset
becomes your native
tongue.
Throughout,
I'll be taking the attitude
that you want to build a
model in
your
head that allows you to
develop a deep understanding of
the
language;
if you encounter a puzzle
you'll be able to feed it to
your model
and
deduce the answer.
Prerequisites
This
book assumes that you
have some programming
familiarity: you
understand
that a program is a collection of
statements, the idea of
a
subroutine/function/macro,
control statements such as
"if" and looping
constructs
such as "while," etc.
However, you might have
learned this in
many
places, such as programming
with a macro language or
working
with
a tool like Perl. As long as
you've programmed to the
point where you
feel
comfortable with the basic
ideas of programming, you'll be
able to
work
through this book. Of
course, the book will be
easier
for
the C
programmers
and more so for the
C++ programmers, but don't
count
yourself
out if you're not
experienced with those
languages (but come
9
willing
to work hard; also, the
multimedia CD that accompanies
this book
will
bring you up to speed on the
basic C syntax necessary to
learn Java).
I'll
be introducing the concepts of
object-oriented programming
(OOP)
and
Java's basic control
mechanisms, so you'll be exposed to
those, and
the
first exercises will involve
the basic control-flow
statements.
Although
references will often be
made to C and C++ language
features,
these
are not intended to be
insider comments, but
instead to help all
programmers
put Java in perspective with
those languages, from
which,
after
all, Java is descended. I
will attempt to make these
references simple
and
to explain anything that I
think a non- C/C++
programmer would not
be
familiar with.
Learning
Java
At
about the same time
that my first book Using
C++ (Osborne/McGraw-
Hill,
1989) came out, I began
teaching that language.
Teaching
programming
languages has become my
profession; I've seen
nodding
heads,
blank faces, and puzzled
expressions in audiences all
over the
world
since 1989. As I began
giving in-house training
with smaller groups
of
people, I discovered something
during the exercises. Even
those people
who
were smiling and nodding
were confused about many
issues. I found
out,
by chairing the C++ track at
the Software Development
Conference
for
a number of years (and later
the Java track), that I
and other speakers
tended
to give the typical audience
too many topics too
fast. So eventually,
through
both variety in the audience
level and the way
that I presented
the
material, I would end up
losing some portion of the
audience. Maybe
it's
asking too much, but
because I am one of those
people resistant to
traditional
lecturing (and for most
people, I believe, such
resistance
results
from boredom), I wanted to
try to keep everyone up to
speed.
For
a time, I was creating a
number of different presentations in
fairly
short
order. Thus, I ended up
learning by experiment and
iteration (a
technique
that also works well in
Java program design).
Eventually I
developed
a course using everything I
had learned from my
teaching
experience--one
that I would be happy giving
for a long time. It
tackles
the
learning problem in discrete,
easy-to-digest steps, and in a
hands-on
seminar
(the ideal learning
situation) there are
exercises following each
of
10
Thinking
in Java
the
short lessons. I now give
this course in public Java
seminars, which
you
can find out about at
.
(The introductory
seminar
is also available as a CD ROM.
Information is available at
the
same
Web site.)
The
feedback that I get from
each seminar helps me change
and refocus
the
material until I think it
works well as a teaching
medium. But this
book
isn't just seminar notes--I
tried to pack as much
information as I
could
within these pages, and
structured it to draw you
through onto the
next
subject. More than anything,
the book is designed to
serve the
solitary
reader who is struggling
with a new programming
language.
Goals
Like
my previous book Thinking
in C++, this
book has come to be
structured
around the process of
teaching the language. In
particular, my
motivation
is to create something that
provides me with a way to
teach the
language
in my own seminars. When I
think of a chapter in the
book, I
think
in terms of what makes a
good lesson during a
seminar. My goal is
to
get bite-sized pieces that
can be taught in a reasonable
amount of time,
followed
by exercises that are
feasible to accomplish in a
classroom
situation.
My
goals in this book are
to:
1.
Present
the material one simple
step at a time so that you
can easily
digest
each concept before moving
on.
2.
Use
examples that are as simple
and short as possible.
This
sometimes
prevents me from tackling
"real world" problems,
but
I've
found that beginners are
usually happier when they
can
understand
every detail of an example
rather than being
impressed
by
the scope of the problem it
solves. Also, there's a
severe limit to
the
amount of code that can be
absorbed in a classroom
situation.
For
this I will no doubt receive
criticism for using "toy
examples,"
but
I'm willing to accept that
in favor of producing
something
pedagogically
useful.
Introduction
11
3.
Carefully
sequence the presentation of
features so that you
aren't
seeing
something that you haven't
been exposed to. Of course,
this
isn't
always possible; in those
situations, a brief
introductory
description
is given.
4.
Give
you what I think is
important for you to
understand about the
language,
rather than everything I
know. I believe there is
an
information
importance hierarchy, and
that there are some
facts
that
95 percent of programmers will
never need to know and
that
just
confuse people and adds to
their perception of the
complexity
of
the language. To take an
example from C, if you
memorize the
operator
precedence table (I never
did), you can write
clever code.
But
if you need to think about
it, it will also confuse
the
reader/maintainer
of that code. So forget
about precedence, and
use
parentheses when things
aren't clear.
5.
Keep
each section focused enough
so that the lecture
time--and the
time
between exercise periods--is
small. Not only does
this keep
the
audience's minds more active
and involved during a
hands-on
seminar,
but it gives the reader a
greater sense of
accomplishment.
6.
Provide
you with a solid foundation
so that you can understand
the
issues
well enough to move on to
more difficult coursework
and
books.
Online
documentation
The
Java language and libraries
from Sun Microsystems (a
free download)
come
with documentation in electronic
form, readable using a
Web
browser,
and virtually every third
party implementation of Java
has this
or
an equivalent documentation system.
Almost all the books
published
on
Java have duplicated this
documentation. So you either
already have it
or
you can download it,
and unless necessary, this
book will not
repeat
that
documentation because it's
usually much faster if you
find the class
descriptions
with your Web browser
than if you look them up in
a book
(and
the on-line documentation is
probably more up-to-date).
This book
will
provide extra descriptions of
the classes only when
it's necessary to
supplement
the documentation so you can
understand a particular
example.
12
Thinking
in Java
Chapters
This
book was designed with
one thing in mind: the
way people learn
the
Java
language. Seminar audience
feedback helped me understand
the
difficult
parts that needed
illumination. In the areas
where I got ambitious
and
included too many features
all at once, I came to
know--through the
process
of presenting the material--that if
you include a lot of
new
features,
you need to explain them
all, and this easily
compounds the
student's
confusion. As a result, I've
taken a great deal of
trouble to
introduce
the features as few at a
time as possible.
The
goal, then, is for each
chapter to teach a single
feature, or a small
group
of associated features, without
relying on additional features.
That
way
you can digest each
piece in the context of your
current knowledge
before
moving on.
Here
is a brief description of the
chapters contained in the
book, which
correspond
to lectures and exercise
periods in my hands-on
seminars.
Chapter
1:
Introduction
to Objects
This
chapter is an overview of what
object-oriented
programming
is all about, including the
answer to the basic
question
"What's an object?", interface
vs. implementation,
abstraction
and encapsulation, messages
and functions,
inheritance
and composition, and the
all-important
polymorphism.
You'll also get an overview
of issues of object
creation
such as constructors, where
the objects live, where
to
put
them once they're created,
and the magical
garbage
collector
that cleans up the objects
that are no longer
needed.
Other
issues will be introduced,
including error handling
with
exceptions,
multithreading for responsive
user interfaces, and
networking
and the Internet. You'll
learn what makes
Java
special,
why it's been so successful,
and about
object-oriented
analysis
and design.
Chapter
2:
Everything
is an Object
This
chapter moves you to the
point where you can
write your
first
Java program, so it must
give an overview of
the
essentials,
including the concept of a
reference
to
an object;
Introduction
13
how
to create an object; an introduction to
primitive types
and
arrays; scoping and the
way objects are destroyed by
the
garbage
collector; how everything in
Java is a new data
type
(class)
and how to create your
own classes;
functions,
arguments,
and return values; name
visibility and using
components
from other libraries; the
static
keyword;
and
comments
and embedded
documentation.
Chapter
3:
Controlling
Program Flow
This
chapter begins with all of
the operators that come to
Java
from
C and C++. In addition,
you'll discover
common
operator
pitfalls, casting, promotion,
and precedence. This
is
followed
by the basic control-flow
and selection
operations
that
you get with virtually
any programming language:
choice
with
if-else; looping with for
and while; quitting a loop
with
break
and continue as well as
Java's labeled break and
labeled
continue
(which account for the
"missing goto" in Java);
and
selection
using switch. Although much
of this material has
common
threads with C and C++
code, there are
some
differences.
In addition, all the
examples will be full
Java
examples
so you'll get more
comfortable with what Java
looks
like.
Chapter
4:
Initialization
& Cleanup
This
chapter begins by introducing
the constructor,
which
guarantees
proper initialization. The
definition of the
constructor
leads into the concept of
function overloading
(since
you might want several
constructors). This is
followed
by
a discussion of the process of
cleanup, which is not
always
as
simple as it seems. Normally,
you just drop an object
when
you're
done with it and the
garbage collector eventually
comes
along
and releases the memory.
This portion explores
the
garbage
collector and some of its
idiosyncrasies. The
chapter
concludes
with a closer look at how
things are
initialized:
automatic
member initialization, specifying
member
initialization,
the order of initialization,
static
initialization
and
array initialization.
14
Thinking
in Java
Chapter
5:
Hiding
the Implementation
This
chapter covers the way
that code is packaged
together,
and
why some parts of a library
are exposed while other
parts
are
hidden. It begins by looking at
the package
and
import
keywords,
which perform file-level
packaging and allow
you
to
build libraries of classes. It
then examines subject
of
directory
paths and file names.
The remainder of the
chapter
looks
at the public,
private,
and
protected
keywords,
the
concept
of "friendly" access, and
what the different levels
of
access
control mean when used in
various contexts.
Chapter
6:
Reusing
Classes
The
concept of inheritance is standard in
virtually all OOP
languages.
It's a way to take an
existing class and add to
its
functionality
(as well as change it,
the subject of Chapter
7).
Inheritance
is often a way to reuse code
by leaving the "base
class"
the same, and just
patching things here and
there to
produce
what you want. However,
inheritance isn't the
only
way
to make new classes from
existing ones. You can
also
embed
an object inside your new
class with composition.
In
this
chapter you'll learn about
these two ways to reuse
code in
Java,
and how to apply
them.
Chapter
7:
Polymorphism
On
your own, you might
take nine months to discover
and
understand
polymorphism, a cornerstone of OOP.
Through
small,
simple examples you'll see
how to create a family
of
types
with inheritance and
manipulate objects in that
family
through
their common base class.
Java's polymorphism
allows
you to treat all objects in
this family generically,
which
means
the bulk of your code
doesn't rely on specific
type
information.
This makes your programs
extensible, so
building
programs and code
maintenance is easier
and
cheaper.
Chapter
8:
Interfaces
& Inner Classes
Java
provides a third way to set
up a reuse relationship,
through
the interface,
which is a pure abstraction of
the
interface
of an object. The interface
is
more than just an
Introduction
15
abstract
class taken to the extreme,
since it allows you
to
perform
a variation on C++'s "multiple
inheritance," by
creating
a class that can be upcast
to more than one base
type.
At
first, inner classes look
like a simple code
hiding
mechanism:
you place classes inside
other classes. You'll
learn,
however, that the inner
class does more than
that--it
knows
about and can communicate
with the surrounding
class--and
that the kind of code
you can write with
inner
classes
is more elegant and clear,
although it is a new
concept
to
most and takes some
time to become comfortable
with
design
using inner classes.
Chapter
9:
Holding
your Objects
It's
a fairly simple program that
has only a fixed quantity
of
objects
with known lifetimes. In
general, your programs
will
always
be creating new objects at a
variety of times that
will
be
known only while the
program is running. In addition,
you
won't
know until run-time the
quantity or even the exact
type
of
the objects you need. To
solve the general
programming
problem,
you need to create any
number of objects,
anytime,
anywhere.
This chapter explores in
depth the container
library
that Java 2 supplies to hold
objects while you're
working
with them: the simple
arrays and more
sophisticated
containers
(data structures) such as
ArrayList
and
HashMap.
Chapter
10: Error Handling with Exceptions
The
basic philosophy of Java is
that badly-formed code
will
not
be run. As much as possible,
the compiler catches
problems,
but sometimes the
problems--either programmer
error
or a natural error condition
that occurs as part of
the
normal
execution of the program--can be
detected and dealt
with
only at run-time. Java has
exception
handling to
deal
with
any problems that arise
while the program is
running.
This
chapter examines how the
keywords try,
catch,
throw,
throws,
and finally
work
in Java; when you should
throw
exceptions
and what to do when you
catch them. In
addition,
you'll
see Java's standard
exceptions, how to create
your own,
16
Thinking
in Java
what
happens with exceptions in
constructors, and how
exception
handlers are located.
Chapter
11:
The
Java I/O System
Theoretically,
you can divide any
program into three
parts:
input,
process, and output. This
implies that I/O
(input/output)
is an important part of the
equation. In this
chapter
you'll learn about the
different classes that
Java
provides
for reading and writing
files, blocks of memory,
and
the
console. The distinction
between "old" I/O and
"new"
Java
I/O will be shown. In
addition, this chapter
examines the
process
of taking an object, "streaming" it
(so that it can be
placed
on disk or sent across a
network) and reconstructing
it,
which
is handled for you with
Java's object
serialization.
Also,
Java's
compression libraries, which
are used in the
Java
ARchive
file format (JAR), are
examined.
Chapter
12:
Run-Time
Type Identification
Java
run-time type identification
(RTTI) lets you find
the
exact
type of an object when you
have a reference to only
the
base
type. Normally, you'll want
to intentionally ignore
the
exact
type of an object and let
Java's dynamic
binding
mechanism
(polymorphism) implement the
correct behavior
for
that type. But occasionally
it is very helpful to know
the
exact
type of an object for which
you have only a
base
reference.
Often this information
allows you to perform
a
special-case
operation more efficiently.
This chapter explains
what
RTTI is for, how to use
it, and how to get
rid of it when it
doesn't
belong there. In addition,
this chapter introduces
the
Java
reflection
mechanism.
Chapter
13:
Creating
Windows and Applets
Java
comes with the "Swing" GUI
library, which is a set
of
classes
that handle windowing in a
portable fashion.
These
windowed
programs can either be
applets or stand-alone
applications.
This chapter is an introduction to
Swing and the
creation
of World Wide Web applets.
The important
"JavaBeans"
technology is introduced. This is
fundamental
Introduction
17
for
the creation of Rapid-Application
Development (RAD)
program-building
tools.
Chapter
14: Multiple Threads
Java
provides a built-in facility to
support multiple
concurrent
subtasks, called threads,
running within a
single
program.
(Unless you have multiple
processors on your
machine,
this is only the appearance
of
multiple subtasks.)
Although
these can be used anywhere,
threads are most
apparent
when trying to create a
responsive user interface
so,
for
example, a user isn't
prevented from pressing a
button or
entering
data while some processing
is going on. This
chapter
looks
at the syntax and semantics
of multithreading in Java.
Chapter
15:
Distributed
Computing
All
the Java features and
libraries seem to really
come
together
when you start writing
programs to work
across
networks.
This chapter explores
communication across
networks
and the Internet, and
the classes that Java
provides
to
make this easier. It
introduces the very
important concepts
of
Servlets
and
JSPs
(for
server-side programming),
along
with
Java DataBase Connectivity
(JDBC),
and Remote
Method
Invocation (RMI).
Finally, there's an introduction
to
the
new technologies of JINI,
JavaSpaces,
and Enterprise
JavaBeans
(EJBs).
Appendix
A: Passing & Returning Objects
Since
the only way you
talk to objects in Java is
through
references,
the concepts of passing an
object into a
function
and
returning an object from a
function have some
interesting
consequences.
This appendix explains what
you need to know
to
manage objects when you're
moving in and out of
functions,
and also shows the
String
class,
which uses a
different
approach to the
problem.
Appendix
B: The Java Native Interface (JNI)
A
totally portable Java
program has serious
drawbacks: speed
and
the inability to access
platform-specific services.
When
you
know the platform that
you're running on, it's
possible to
18
Thinking
in Java
dramatically
speed up certain operations by
making them
native
methods, which
are functions that are
written in
another
programming language (currently,
only C/C++ is
supported).
This appendix gives you
enough of an
introduction
to this feature that you
should be able to
create
simple
examples that interface with
non-Java code.
Appendix
C: Java Programming Guidelines
This
appendix contains suggestions to
help guide you
while
performing
low-level program design and
writing code.
Appendix
D: Recommended Reading
A
list of some of the Java
books I've found
particularly useful.
Exercises
I've
discovered that simple
exercises are exceptionally
useful to complete
a
student's understanding during a
seminar, so you'll find a
set at the end
of
each chapter.
Most
exercises are designed to be
easy enough that they
can be finished in
a
reasonable amount of time in a
classroom situation while
the instructor
observes,
making sure that all
the students are absorbing
the material.
Some
exercises are more advanced
to prevent boredom for
experienced
students.
The majority are designed to
be solved in a short time
and test
and
polish your knowledge. Some
are more challenging, but
none present
major
challenges. (Presumably, you'll
find those on your own--or
more
likely
they'll find you).
Solutions
to selected exercises can be
found in the electronic
document
The
Thinking in Java Annotated
Solution Guide, available
for a small fee
from
.
Multimedia
CD ROM
There
are two multimedia CDs
associated with this book.
The first is
bound
into the book itself:
Thinking
in C,
described at the end of
the
preface,
which prepares you for
the book by bringing you up
to speed on
the
necessary C syntax you need
to be able to understand
Java.
Introduction
19
A
second Multimedia CD ROM is
available, which is based on
the contents
of
the book. This CD ROM is a
separate product and
contains the entire
contents
of the week-long "Hands-On
Java" training seminar. This
is
more
than 15 hours of lectures
that I have recorded,
synchronized with
hundreds
of slides of information. Because
the seminar is based on
this
book,
it is an ideal accompaniment.
The
CD ROM contains all the
lectures (with the important
exception of
personalized
attention!) from the
five-day full-immersion
training
seminars.
We believe that it sets a
new standard for
quality.
The
Hands-On Java CD ROM is
available only by ordering
directly from
the
Web site .
Source
code
All
the source code for
this book is available as
copyrighted freeware,
distributed
as a single package, by visiting
the Web site
.
To make sure that you
get the most
current
version,
this is the official site
for distribution of the code
and the
electronic
version of the book. You
can find mirrored versions
of the
electronic
book and the code on
other sites (some of these
sites are found
at
),
but you should check
the official site to
ensure
that
the mirrored version is
actually the most recent
edition. You may
distribute
the code in classroom and
other educational
situations.
The
primary goal of the
copyright is to ensure that
the source of the
code
is
properly cited, and to
prevent you from
republishing the code in
print
media
without permission. (As long
as the source is cited,
using examples
from
the book in most media is
generally not a
problem.)
In
each source code file
you will find a reference to
the following
copyright
notice:
//:!
:CopyRight.txt
Copyright
©2000 Bruce Eckel
Source
code file from the
2nd edition of the
book
"Thinking
in Java." All rights
reserved EXCEPT as
allowed
by the following
statements:
You
can freely use this
file
20
Thinking
in Java
for
your own work (personal or
commercial),
including
modifications and distribution
in
executable
form only. Permission is
granted to use
this
file in classroom situations,
including its
use
in presentation materials, as long as
the book
"Thinking
in Java" is cited as the
source.
Except
in classroom situations, you
cannot copy
and
distribute this code;
instead, the sole
distribution
point is http://
(and
official mirror sites) where
it is
freely
available. You cannot remove
this
copyright
and notice. You cannot
distribute
modified
versions of the source code
in this
package.
You cannot use this
file in printed
media
without the express
permission of the
author.
Bruce Eckel makes no
representation about
the
suitability of this software
for any purpose.
It
is provided "as is" without
express or implied
warranty
of any kind, including any
implied
warranty
of merchantability, fitness for
a
particular
purpose or non-infringement. The
entire
risk
as to the quality and
performance of the
software
is with you. Bruce Eckel
and the
publisher
shall not be liable for
any damages
suffered
by you or any third party as
a result of
using
or distributing software. In no event
will
Bruce
Eckel or the publisher be
liable for any
lost
revenue, profit, or data, or
for direct,
indirect,
special, consequential, incidental,
or
punitive
damages, however caused and
regardless of
the
theory of liability, arising
out of the use of
or
inability to use software,
even if Bruce Eckel
and
the publisher have been
advised of the
possibility
of such damages. Should the
software
prove
defective, you assume the
cost of all
necessary
servicing, repair, or correction. If
you
think
you've found an error,
please submit the
correction
using the form you
will find at
.
(Please use the
same
form
for non-code errors found in
the book.)
///:~
Introduction
21
You
may use the code in
your projects and in the
classroom (including
your
presentation materials) as long as
the copyright notice that
appears
in
each source file is
retained.
Coding
standards
In
the text of this book,
identifiers (function, variable,
and class names)
are
set in bold.
Most keywords are also
set in bold, except for
those
keywords
that are used so much
that the bolding can
become tedious,
such
as "class."
I
use a particular coding
style for the examples in
this book. This
style
follows
the style that Sun
itself uses in virtually all
of the code you
will
find
at its site (see java.sun.com/docs/codeconv/index.html),
and seems
to
be supported by most Java
development environments. If you've
read
my
other works, you'll also
notice that Sun's coding
style coincides with
mine--this
pleases me, although I had
nothing to do with it. The
subject of
formatting
style is good for hours of
hot debate, so I'll just
say I'm not
trying
to dictate correct style via
my examples; I have my own
motivation
for
using the style that I
do. Because Java is a
free-form programming
language,
you can continue to use
whatever style you're
comfortable with.
The
programs in this book are
files that are included by
the word
processor
in the text, directly from
compiled files. Thus, the
code files
printed
in the book should all
work without compiler
errors. The errors
that
should
cause
compile-time error messages
are commented out
with
the
comment //!
so
they can be easily
discovered and tested
using
automatic
means. Errors discovered and
reported to the author
will
appear
first in the distributed
source code and later in
updates of the book
(which
will also appear on the
Web site ).
Java
versions
I
generally rely on the Sun
implementation of Java as a reference
when
determining
whether behavior is
correct.
Over
time, Sun has released
three major versions of
Java: 1.0, 1.1 and
2
(which
is called version 2 even
though the releases of the
JDK from Sun
continue
to use the numbering scheme
of 1.2, 1.3, 1.4, etc.).
Version 2
22
Thinking
in Java
seems
to finally bring Java into
the prime time, in
particular where user
interface
tools are concerned. This
book focuses on and is
tested with Java
2,
although I do sometimes make
concessions to earlier features of
Java 2
so
that the code will
compile under Linux (via
the Linux JDK that
was
available
at this writing).
If
you need to learn about
earlier releases of the
language that are
not
covered
in this edition, the first
edition of the book is
freely downloadable
at
and
is also contained on the CD
that is bound in
with
this book.
One
thing you'll notice is that,
when I do need to mention
earlier versions
of
the language, I don't use
the sub-revision numbers. In
this book I will
refer
to Java 1.0, Java 1.1,
and Java 2 only, to guard
against typographical
errors
produced by further sub-revisioning of
these products.
Seminars
and mentoring
My
company provides five-day,
hands-on, public and
in-house training
seminars
based on the material in
this book. Selected material
from each
chapter
represents a lesson, which is
followed by a monitored
exercise
period
so each student receives
personal attention. The
audio lectures and
slides
for the introductory seminar
are also captured on CD ROM
to
provide
at least some of the
experience of the seminar
without the travel
and
expense. For more
information, go to .
My
company also provides
consulting, mentoring and
walkthrough
services
to help guide your project
through its development
cycle--
especially
your company's first Java
project.
Errors
No
matter how many tricks a
writer uses to detect
errors, some always
creep
in and these often leap
off the page for a
fresh reader.
There
is an error submission form
linked from the beginning of
each
chapter
in the HTML version of this
book (and on the CD ROM
bound
into
the back of this book,
and downloadable from
)
and
also on the Web site
itself, on the page for
this book. If you
discover
Introduction
23
anything
you believe to be an error,
please use this form to
submit the
error
along with your suggested
correction. If necessary, include
the
original
source file and note
any suggested modifications.
Your help is
appreciated.
Note
on the cover design
The
cover of Thinking
in Java is inspired by
the American Arts &
Crafts
Movement,
which began near the
turn of the century and
reached its
zenith
between 1900 and 1920. It
began in England as a reaction to
both
the
machine production of the
Industrial Revolution and
the highly
ornamental
style of the Victorian era.
Arts & Crafts emphasized
spare
design,
the forms of nature as seen
in the art nouveau movement,
hand-
crafting,
and the importance of the
individual craftsperson, and
yet it did
not
eschew the use of modern
tools. There are many
echoes with the
situation
we have today: the turn of
the century, the evolution
from the
raw
beginnings of the computer
revolution to something more
refined and
meaningful
to individual persons, and
the emphasis on
software
craftsmanship
rather than just
manufacturing code.
I
see Java in this same
way: as an attempt to elevate
the programmer
away
from an operating-system mechanic
and toward being a
"software
craftsman."
Both
the author and the
book/cover designer (who
have been friends
since
childhood) find inspiration in
this movement, and both
own
furniture,
lamps, and other pieces
that are either original or
inspired by
this
period.
The
other theme in this cover
suggests a collection box
that a naturalist
might
use to display the insect
specimens that he or she has
preserved.
These
insects are objects, which
are placed within the
box objects. The
box
objects are themselves
placed within the "cover
object," which
illustrates
the fundamental concept of
aggregation in object-oriented
programming.
Of course, a programmer cannot
help but make
the
association
with "bugs," and here
the bugs have been
captured and
presumably
killed in a specimen jar,
and finally confined within
a small
display
box, as if to imply Java's
ability to find, display,
and subdue bugs
(which
is truly one of its most
powerful attributes).
24
Thinking
in Java
Acknowledgements
First,
thanks to associates who
have worked with me to give
seminars,
provide
consulting, and develop
teaching projects: Andrea
Provaglio,
Dave
Bartlett (who also
contributed significantly to Chapter
15), Bill
Venners,
and Larry O'Brien. I
appreciate your patience as I
continue to try
to
develop the best model
for independent folks like
us to work together.
Thanks
to Rolf André Klaedtke
(Switzerland); Martin Vlcek,
Martin Byer,
Vlada
& Pavel Lahoda, Martin
the Bear, and Hanka
(Prague); and Marco
Cantu
(Italy) for hosting me on my
first self-organized European
seminar
tour.
Thanks
to the Doyle Street
Cohousing Community for
putting up with me
for
the two years that it
took me to write the first
edition of this book
(and
for
putting up with me at all).
Thanks very much to Kevin
and Sonda
Donovan
for subletting their great
place in gorgeous Crested
Butte,
Colorado
for the summer while I
worked on the first edition
of the book.
Also
thanks to the friendly
residents of Crested Butte
and the Rocky
Mountain
Biological Laboratory who
make me feel so
welcome.
Thanks
to Claudette Moore at Moore
Literary Agency for her
tremendous
patience
and perseverance in getting me
exactly what I
wanted.
My
first two books were
published with Jeff Pepper
as editor at
Osborne/McGraw-Hill.
Jeff appeared at the right
place and the right
time
at
Prentice-Hall and has
cleared the path and
made all the right
things
happen
to make this a very pleasant
publishing experience. Thanks,
Jeff--
it
means a lot to me.
I'm
especially indebted to Gen
Kiyooka and his company
Digigami, who
graciously
provided my Web server for
the first several years of
my
presence
on the Web. This was an
invaluable learning
aid.
Thanks
to Cay Horstmann (co-author of
Core
Java,
Prentice-Hall, 2000),
D'Arcy
Smith (Symantec), and Paul
Tyma (co-author of Java
Primer Plus,
The
Waite Group, 1996), for
helping me clarify concepts in
the language.
Introduction
25
Thanks
to people who have spoken in
my Java track at the
Software
Development
Conference, and students in my
seminars, who ask
the
questions
I need to hear in order to
make the material more
clear.
Special
thanks to Larry and Tina
O'Brien, who helped turn my
seminar
into
the original Hands-On
Java CD ROM.
(You can find out
more at
.)
Lots
of people sent in corrections
and I am indebted to them
all, but
particular
thanks go to (for the first
edition): Kevin Raulerson
(found tons
of
great bugs), Bob Resendes
(simply incredible), John
Pinto, Joe Dante,
Joe
Sharp (all three were
fabulous), David Combs (many
grammar and
clarification
corrections), Dr. Robert
Stephenson, John Cook,
Franklin
Chen,
Zev Griner, David Karr,
Leander A. Stroschein, Steve
Clark, Charles
A.
Lee, Austin Maher, Dennis P.
Roth, Roque Oliveira,
Douglas Dunn,
Dejan
Ristic, Neil Galarneau,
David B. Malkovsky, Steve
Wilkinson, and a
host
of others. Prof. Ir. Marc
Meurrens put in a great deal
of effort to
publicize
and make the electronic
version of the first edition
of the book
available
in Europe.
There
have been a spate of smart
technical people in my life
who have
become
friends and have also
been both influential and
unusual in that
they
do yoga and practice other
forms of spiritual enhancement,
which I
find
quite inspirational and
instructional. They are
Kraig Brockschmidt,
Gen
Kiyooka, and Andrea
Provaglio, (who helps in the
understanding of
Java
and programming in general in
Italy, and now in the
United States as
an
associate of the MindView
team).
It's
not that much of a surprise
to me that understanding Delphi
helped
me
understand Java, since there
are many concepts and
language design
decisions
in common. My Delphi friends
provided assistance by
helping
me
gain insight into that
marvelous programming environment.
They are
Marco
Cantu (another Italian--perhaps
being steeped in Latin gives
one
aptitude
for programming languages?),
Neil Rubenking (who used to
do
the
yoga/vegetarian/Zen thing until he
discovered computers), and
of
course
Zack Urlocker, a long-time
pal whom I've traveled
the world with.
My
friend Richard Hale Shaw's
insights and support have
been very
helpful
(and Kim's, too). Richard
and I spent many months
giving
seminars
together and trying to work
out the perfect learning
experience
26
Thinking
in Java
for
the attendees. Thanks also
to KoAnn Vikoren, Eric
Faurot, Marco
Pardi,
and the rest of the
cast and crew at MFI.
Thanks especially to
Tara
Arrowood,
who re-inspired me about the
possibilities of conferences.
The
book design, cover design,
and cover photo were
created by my friend
Daniel
Will-Harris, noted author
and designer (www.Will-Harris.com),
who
used to play with rub-on
letters in junior high
school while he
awaited
the invention of computers
and desktop publishing,
and
complained
of me mumbling over my algebra
problems. However, I
produced
the camera-ready pages
myself, so the typesetting
errors are
mine.
Microsoft® Word 97 for Windows
was used to write the
book and to
create
camera-ready pages in Adobe
Acrobat; the book was
created
directly
from the Acrobat PDF
files. (As a tribute to the
electronic age, I
happened
to be overseas both times
the final version of the
book was
produced--the
first edition was sent
from Capetown, South Africa
and the
second
edition was posted from
Prague). The body typeface
is Georgia
and
the headlines are in
Verdana.
The cover typeface is
ITC
Rennie
Mackintosh.
Thanks
to the vendors who created
the compilers: Borland,
the
Blackdown
group (for Linux), and of
course, Sun.
A
special thanks to all my
teachers and all my students
(who are my
teachers
as well). The most fun
writing teacher was
Gabrielle Rico
(author
of
Writing
the Natural Way, Putnam,
1983). I'll always treasure
the
terrific
week at Esalen.
The
supporting cast of friends
includes, but is not limited
to: Andrew
Binstock,
Steve Sinofsky, JD Hildebrandt,
Tom Keffer, Brian
McElhinney,
Brinkley
Barr, Bill Gates at
Midnight
Engineering Magazine,
Larry
Constantine
and Lucy Lockwood, Greg
Perry, Dan Putterman,
Christi
Westphal,
Gene Wang, Dave Mayer,
David Intersimone,
Andrea
Rosenfield,
Claire Sawyers, more
Italians (Laura Fallai,
Corrado, Ilsa, and
Cristina
Giustozzi), Chris and Laura
Strand, the Almquists, Brad
Jerbic,
Marilyn
Cvitanic, the Mabrys, the
Haflingers, the Pollocks,
Peter Vinci,
the
Robbins Families, the
Moelter Families (and the
McMillans), Michael
Wilk,
Dave Stoner, Laurie Adams,
the Cranstons, Larry Fogg,
Mike and
Karen
Sequeira, Gary Entsminger
and Allison Brody, Kevin
Donovan and
Sonda
Eastlack, Chester and
Shannon Andersen, Joe Lordi,
Dave and
Introduction
27
Brenda
Bartlett, David Lee, the
Rentschlers, the Sudeks,
Dick, Patty, and
Lee
Eckel, Lynn and Todd,
and their families. And of
course, Mom and
Dad.
Internet
contributors
Thanks
to those who helped me
rewrite the examples to use
the Swing
library,
and for other assistance:
Jon Shvarts, Thomas Kirsch,
Rahim
Adatia,
Rajesh Jain, Ravi Manthena,
Banu Rajamani, Jens Brandt,
Nitin
Shivaram,
Malcolm Davis, and everyone
who expressed support.
This
really
helped me jump-start the
project.
28
Thinking
in Java
1:
Introduction
to
Objects
The
genesis of the computer
revolution was in a
machine.
The
genesis of our programming
languages thus tends
to
look
like that machine.
But
computers are not so much
machines as they are mind
amplification
tools
("bicycles for the mind," as
Steve Jobs is fond of
saying) and a
different
kind of expressive medium. As a
result, the tools are
beginning
to
look less like machines
and more like parts of
our minds, and also
like
other
forms of expression such as
writing, painting, sculpture,
animation,
and
filmmaking. Object-oriented programming
(OOP) is part of this
movement
toward using the computer as
an expressive medium.
This
chapter will introduce you
to the basic concepts of
OOP, including an
overview
of development methods. This
chapter, and this book,
assume
that
you have had experience in a
procedural programming
language,
although
not necessarily C. If you
think you need more
preparation in
programming
and the syntax of C before
tackling this book, you
should
work
through the Thinking
in C: Foundations for C++ and Java
training
CD
ROM, bound in with this
book and also available
at
.
This
chapter is background and
supplementary material. Many
people do
not
feel comfortable wading into
object-oriented programming
without
understanding
the big picture first.
Thus, there are many
concepts that
are
introduced here to give you
a solid overview of OOP.
However, many
other
people don't get the
big picture concepts until
they've seen some of
the
mechanics first; these
people may become bogged
down and lost
without
some code to get their
hands on. If you're part of
this latter group
and
are eager to get to the
specifics of the language,
feel free to jump
past
this
chapter--skipping it at this point
will not prevent you
from writing
programs
or learning the language.
However, you will want to
come back
29
here
eventually to fill in your
knowledge so you can
understand why
objects
are important and how to
design with them.
The
progress of
abstraction
All
programming languages provide
abstractions. It can be argued
that
the
complexity of the problems
you're able to solve is
directly related to
the
kind and quality of
abstraction. By "kind" I mean,
"What is it that you
are
abstracting?" Assembly language is a
small abstraction of
the
underlying
machine. Many so-called
"imperative" languages that
followed
(such
as Fortran, BASIC, and C)
were abstractions of assembly
language.
These
languages are big
improvements over assembly
language, but their
primary
abstraction still requires
you to think in terms of the
structure of
the
computer rather than the
structure of the problem you
are trying to
solve.
The programmer must
establish the association
between the
machine
model (in the "solution
space," which is the place
where you're
modeling
that problem, such as a
computer) and the model of
the
problem
that is actually being
solved (in the "problem
space," which is the
place
where the problem exists).
The effort required to
perform this
mapping,
and the fact that it is
extrinsic to the programming
language,
produces
programs that are difficult
to write and expensive to
maintain,
and
as a side effect created the
entire "programming methods"
industry.
The
alternative to modeling the
machine is to model the
problem you're
trying
to solve. Early languages
such as LISP and APL
chose particular
views
of the world ("All problems
are ultimately lists" or
"All problems are
algorithmic,"
respectively). PROLOG casts
all problems into chains
of
decisions.
Languages have been created
for constraint-based
programming
and for programming
exclusively by manipulating
graphical
symbols.
(The latter proved to be too
restrictive.) Each of
these
approaches
is a good solution to the
particular class of problem
they're
designed
to solve, but when you
step outside of that domain
they become
awkward.
The
object-oriented approach goes a
step further by providing
tools for
the
programmer to represent elements in
the problem space.
This
30
Thinking
in Java
representation
is general enough that the
programmer is not
constrained
to
any particular type of
problem. We refer to the
elements in the
problem
space
and their representations in
the solution space as
"objects." (Of
course,
you will also need
other objects that don't
have problem-space
analogs.)
The idea is that the
program is allowed to adapt
itself to the
lingo
of the problem by adding new
types of objects, so when
you read the
code
describing the solution,
you're reading words that
also express the
problem.
This is a more flexible and
powerful language abstraction
than
what
we've had before. Thus,
OOP allows you to describe
the problem in
terms
of the problem, rather than
in terms of the computer
where the
solution
will run. There's still a
connection back to the
computer, though.
Each
object looks quite a bit
like a little computer; it
has a state, and it
has
operations
that you can ask it to
perform. However, this
doesn't seem like
such
a bad analogy to objects in
the real world--they all
have
characteristics
and behaviors.
Some
language designers have
decided that object-oriented
programming
by
itself is not adequate to
easily solve all programming
problems, and
advocate
the combination of various
approaches into multiparadigm
programming
languages.1
Alan
Kay summarized five basic
characteristics of Smalltalk, the
first
successful
object-oriented language and
one of the languages upon
which
Java
is based. These characteristics
represent a pure approach to
object-
oriented
programming:
1.
Everything
is an object. Think
of an object as a fancy
variable;
it stores data, but you
can "make requests" to that
object,
asking
it to perform operations on itself. In
theory, you can
take
any
conceptual component in the
problem you're trying to
solve
(dogs,
buildings, services, etc.)
and represent it as an object in
your
program.
2.
A
program is a bunch of objects telling each other
what
to do by sending messages. To make a
request of an
object,
you "send a message" to that
object. More concretely,
you
1
See
Multiparadigm
Programming in Leda by Timothy
Budd (Addison-Wesley 1995).
Chapter
1: Introduction to Objects
31
can
think of a message as a request to
call a function that
belongs
to
a particular object.
3.
Each
object has its own memory made up of other
objects.
Put another way, you
create a new kind of object
by
making
a package containing existing
objects. Thus, you can
build
complexity
in a program while hiding it
behind the simplicity
of
objects.
4.
Every
object has a type. Using
the parlance, each object is
an
instance
of
a class,
in which "class" is synonymous
with "type." The
most
important distinguishing characteristic
of a class is "What
messages
can you send to
it?"
5.
All
objects of a particular type can receive the same
messages.
This is actually a loaded
statement, as you will
see
later.
Because an object of type
"circle" is also an object of
type
"shape,"
a circle is guaranteed to accept
shape messages. This
means
you can write code
that talks to shapes and
automatically
handle
anything that fits the
description of a shape.
This
substitutability
is
one of the most powerful
concepts in OOP.
An
object has an interface
Aristotle
was probably the first to
begin a careful study of the
concept of
type;
he
spoke of "the class of
fishes and the class of
birds." The idea
that
all
objects, while being unique,
are also part of a class of
objects that have
characteristics
and behaviors in common was
used directly in the
first
object-oriented
language, Simula-67, with
its fundamental keyword
class
that
introduces a new type into a
program.
Simula,
as its name implies, was
created for developing
simulations such
as
the classic "bank teller
problem." In this, you have
a bunch of tellers,
customers,
accounts, transactions, and
units of money--a lot of
"objects."
Objects
that are identical except
for their state during a
program's
execution
are grouped together into
"classes of objects" and
that's where
the
keyword class
came
from. Creating abstract data
types (classes) is a
fundamental
concept in object-oriented programming.
Abstract data
types
work almost exactly like
built-in types: You can
create variables of a
32
Thinking
in Java
type
(called objects
or
instances
in
object-oriented parlance)
and
manipulate
those variables (called
sending
messages or requests;
you
send
a message and the object
figures out what to do with
it). The
members
(elements) of each class
share some commonality:
every account
has
a balance, every teller can
accept a deposit, etc. At
the same time,
each
member
has its own state,
each account has a different
balance, each
teller
has a name. Thus, the
tellers, customers, accounts,
transactions,
etc.,
can each be represented with
a unique entity in the
computer
program.
This entity is the object,
and each object belongs to a
particular
class
that defines its
characteristics and
behaviors.
So,
although what we really do in
object-oriented programming is
create
new
data types, virtually all
object-oriented programming languages
use
the
"class" keyword. When you
see the word "type"
think "class" and
vice
versa2.
Since
a class describes a set of
objects that have identical
characteristics
(data
elements) and behaviors
(functionality), a class is really a
data type
because
a floating point number, for
example, also has a set
of
characteristics
and behaviors. The
difference is that a programmer
defines
a
class to fit a problem
rather than being forced to
use an existing data
type
that was designed to
represent a unit of storage in a
machine. You
extend
the programming language by
adding new data types
specific to
your
needs. The programming
system welcomes the new
classes and gives
them
all the care and
type-checking that it gives to
built-in types.
The
object-oriented approach is not
limited to building
simulations.
Whether
or not you agree that
any program is a simulation of
the system
you're
designing, the use of OOP
techniques can easily reduce
a large set
of
problems to a simple
solution.
Once
a class is established, you
can make as many objects of
that class as
you
like, and then manipulate
those objects as if they are
the elements
that
exist in the problem you
are trying to solve. Indeed,
one of the
challenges
of object-oriented programming is to
create a one-to-one
2
Some people
make a distinction, stating that
type determines the interface while class
is
a
particular implementation of that
interface.
Chapter
1: Introduction to Objects
33
mapping
between the elements in the
problem space and objects in
the
solution
space.
But
how do you get an object to
do useful work for you?
There must be a
way
to make a request of the
object so that it will do
something, such as
complete
a transaction, draw something on
the screen, or turn on
a
switch.
And each object can
satisfy only certain
requests. The requests
you
can
make of an object are
defined by its interface,
and
the type is what
determines
the interface. A simple
example might be a representation of
a
light
bulb:
Light
Type
Name
on()
off()
Interface
brighten()
dim()
Light
lt = new Light();
lt.on();
The
interface establishes what
requests
you can make for a
particular
object.
However, there must be code
somewhere to satisfy that
request.
This,
along with the hidden
data, comprises the
implementation.
From a
procedural
programming standpoint, it's
not that complicated. A type
has
a
function associated with
each possible request, and
when you make a
particular
request to an object, that
function is called. This
process is
usually
summarized by saying that
you "send a message" (make a
request)
to
an object, and the object
figures out what to do with
that message (it
executes
code).
Here,
the name of the type/class
is Light,
the name of this
particular
Light
object
is lt,
and the requests that
you can make of a Light object
are
to turn it on, turn it off,
make it brighter, or make it
dimmer. You
create
a Light
object
by defining a "reference" (lt)
for that object
and
calling
new to
request a new object of that
type. To send a message to
the
object,
you state the name of
the object and connect it to
the message
request
with a period (dot). From
the standpoint of the user
of a
34
Thinking
in Java
predefined
class, that's pretty much
all there is to programming
with
objects.
The
diagram shown above follows
the format of the Unified
Modeling
Language
(UML).
Each class is represented by a
box, with the type
name
in
the top portion of the
box, any data members
that you care to
describe
in
the middle portion of the
box, and the member
functions (the
functions
that
belong to this object, which
receive any messages you
send to that
object)
in the bottom portion of the
box. Often, only the
name of the class
and
the public member functions
are shown in UML design
diagrams, and
so
the middle portion is not
shown. If you're interested
only in the class
name,
then the bottom portion
doesn't need to be shown,
either.
The
hidden
implementation
It
is helpful to break up the
playing field into class
creators (those
who
create
new data types) and
client
programmers3 (the class
consumers
who
use the data types in
their applications). The
goal of the client
programmer
is to collect a toolbox full of
classes to use for
rapid
application
development. The goal of the
class creator is to build a
class
that
exposes only what's
necessary to the client
programmer and keeps
everything
else hidden. Why? Because if
it's hidden, the
client
programmer
can't use it, which
means that the class
creator can change
the
hidden portion at will
without worrying about the
impact to anyone
else.
The hidden portion usually
represents the tender
insides of an object
that
could easily be corrupted by a
careless or uninformed
client
programmer,
so hiding the implementation
reduces program bugs.
The
concept
of implementation hiding cannot be
overemphasized.
In
any relationship it's
important to have boundaries
that are respected by
all
parties involved. When you
create a library, you
establish a
relationship
with the client programmer,
who is also a programmer,
but
3
I'm indebted
to my friend Scott Meyers
for this term.
Chapter
1: Introduction to Objects
35
one
who is putting together an
application by using your
library, possibly
to
build a bigger
library.
If
all the members of a class
are available to everyone,
then the client
programmer
can do anything with that
class and there's no way to
enforce
rules.
Even though you might
really prefer that the
client programmer not
directly
manipulate some of the
members of your class,
without access
control
there's no way to prevent
it. Everything's naked to
the world.
So
the first reason for
access control is to keep
client programmers'
hands
off
portions they shouldn't
touch--parts that are
necessary for the
internal
machinations
of the data type but
not part of the interface
that users need
in
order to solve their
particular problems. This is
actually a service to
users
because they can easily
see what's important to them
and what they
can
ignore.
The
second reason for access
control is to allow the
library designer to
change
the internal workings of the
class without worrying about
how it
will
affect the client
programmer. For example, you
might implement a
particular
class in a simple fashion to
ease development, and then
later
discover
that you need to rewrite it
in order to make it run
faster. If the
interface
and implementation are
clearly separated and
protected, you
can
accomplish this
easily.
Java
uses three explicit keywords
to set the boundaries in a
class: public,
private,
and protected.
Their use and meaning
are quite
straightforward.
These access
specifiers determine
who can use
the
definitions
that follow. public
means
the following definitions
are
available
to everyone. The private
keyword,
on the other hand,
means
that
no one can access those
definitions except you, the
creator of the
type,
inside member functions of
that type. private
is
a brick wall
between
you and the client
programmer. If someone tries to
access a
private
member,
they'll get a compile-time
error. protected
acts
like
private,
with the exception that an
inheriting class has access
to
protected
members,
but not private
members.
Inheritance will be
introduced
shortly.
Java
also has a "default" access,
which comes into play if
you don't use
one
of the aforementioned specifiers.
This is sometimes called
"friendly"
access
because classes can access
the friendly members of
other classes in
36
Thinking
in Java
the
same package, but outside of
the package those same
friendly
members
appear to be private.
Reusing
the
implementation
Once
a class has been created
and tested, it should
(ideally) represent a
useful
unit of code. It turns out
that this reusability is not
nearly so easy to
achieve
as many would hope; it takes
experience and insight to
produce a
good
design. But once you
have such a design, it begs
to be reused. Code
reuse
is one of the greatest
advantages that object-oriented
programming
languages
provide.
The
simplest way to reuse a
class is to just use an
object of that class
directly,
but you can also
place an object of that
class inside a new
class.
We
call this "creating a member
object." Your new class
can be made up of
any
number and type of other
objects, in any combination
that you need
to
achieve the functionality
desired in your new class.
Because you are
composing
a new class from existing
classes, this concept is
called
composition
(or
more generally, aggregation).
Composition is often
referred
to as a "has-a" relationship, as in "a
car has an engine."
Car
Engine
(The
above UML diagram indicates
composition with the filled
diamond,
which
states there is one car. I
will typically use a simpler
form: just a line,
without
the diamond, to indicate an
association.4)
Composition
comes with a great deal of
flexibility. The member
objects of
your
new class are usually
private, making them
inaccessible to the
client
programmers
who are using the
class. This allows you to
change those
4
This is
usually enough detail for
most diagrams, and you don't
need to get specific
about
whether
you're using aggregation or
composition.
Chapter
1: Introduction to Objects
37
members
without disturbing existing
client code. You can
also change the
member
objects at run-time, to dynamically
change the behavior of
your
program.
Inheritance, which is described
next, does not have
this
flexibility
since the compiler must
place compile-time restrictions
on
classes
created with
inheritance.
Because
inheritance is so important in
object-oriented programming it is
often
highly emphasized, and the
new programmer can get
the idea that
inheritance
should be used everywhere.
This can result in awkward
and
overly
complicated designs. Instead,
you should first look to
composition
when
creating new classes, since
it is simpler and more
flexible. If you
take
this approach, your designs
will be cleaner. Once you've
had some
experience,
it will be reasonably obvious
when you need
inheritance.
Inheritance:
reusing
the interface
By
itself, the idea of an
object is a convenient tool. It
allows you to
package
data and functionality
together by concept,
so you can represent
an
appropriate problem-space idea
rather than being forced to
use the
idioms
of the underlying machine.
These concepts are expressed
as
fundamental
units in the programming
language by using the
class
keyword.
It
seems a pity, however, to go to
all the trouble to create a
class and then
be
forced to create a brand new
one that might have
similar functionality.
It's
nicer if we can take the
existing class, clone it,
and then make
additions
and modifications to the
clone. This is effectively
what you get
with
inheritance,
with the exception that if
the original class (called
the
base
or
super
or
parent
class)
is changed, the modified
"clone" (called the
derived
or
inherited
or
sub
or
child
class)
also reflects those
changes.
38
Thinking
in Java
Base
Derived
(The
arrow in the above UML
diagram points from the
derived class to the
base
class. As you will see,
there can be more than
one derived class.)
A
type does more than
describe the constraints on a
set of objects; it
also
has
a relationship with other
types. Two types can
have characteristics
and
behaviors in common, but one
type may contain more
characteristics
than
another and may also
handle more messages (or
handle them
differently).
Inheritance expresses this
similarity between types
using the
concept
of base types and derived
types. A base type contains
all of the
characteristics
and behaviors that are
shared among the types
derived
from
it. You create a base
type to represent the core
of your ideas about
some
objects in your system. From
the base type, you
derive other types to
express
the different ways that
this core can be
realized.
For
example, a trash-recycling machine
sorts pieces of trash. The
base
type
is "trash," and each piece
of trash has a weight, a
value, and so on,
and
can be shredded, melted, or
decomposed. From this, more
specific
types
of trash are derived that
may have additional
characteristics (a
bottle
has a color) or behaviors
(an aluminum can may be
crushed, a steel
can
is magnetic). In addition, some
behaviors may be different
(the value
of
paper depends on its type
and condition). Using
inheritance, you can
build
a type hierarchy that
expresses the problem you're
trying to solve in
terms
of its types.
A
second example is the
classic "shape" example,
perhaps used in a
computer-aided
design system or game
simulation. The base type
is
"shape,"
and each shape has a
size, a color, a position,
and so on. Each
shape
can be drawn, erased, moved,
colored, etc. From this,
specific types
of
shapes are derived
(inherited): circle, square,
triangle, and so on,
each
of
which may have additional
characteristics and behaviors.
Certain
Chapter
1: Introduction to Objects
39
shapes
can be flipped, for example.
Some behaviors may be
different, such
as
when you want to calculate
the area of a shape. The
type hierarchy
embodies
both the similarities and
differences between the
shapes.
Shape
draw()
erase()
move()
getColor()
setColor()
Circle
Square
Triangle
Casting
the solution in the same
terms as the problem is
tremendously
beneficial
because you don't need a
lot of intermediate models to
get from
a
description of the problem to a
description of the solution.
With objects,
the
type hierarchy is the
primary model, so you go
directly from the
description
of the system in the real
world to the description of
the system
in
code. Indeed, one of the
difficulties people have
with object-oriented
design
is that it's too simple to
get from the beginning to
the end. A mind
trained
to look for complex
solutions is often stumped by
this simplicity at
first.
When
you inherit from an existing
type, you create a new
type. This new
type
contains not only all
the members of the existing
type (although the
private
ones
are hidden away and
inaccessible), but more
important, it
duplicates
the interface of the base
class. That is, all
the messages you
can
send
to objects of the base class
you can also send to
objects of the
derived
class.
Since we know the type of a
class by the messages we can
send to it,
this
means that the derived
class is
the same type as the
base class. In
the
previous
example, "a circle is a shape."
This type equivalence
via
inheritance
is one of the fundamental
gateways in understanding
the
meaning
of object-oriented programming.
40
Thinking
in Java
Since
both the base class
and derived class have
the same interface,
there
must
be some implementation to go along
with that interface. That
is,
there
must be some code to execute
when an object receives a
particular
message.
If you simply inherit a
class and don't do anything
else, the
methods
from the base-class
interface come right along
into the derived
class.
That means objects of the
derived class have not
only the same
type,
they
also have the same
behavior, which isn't
particularly interesting.
You
have two ways to
differentiate your new
derived class from
the
original
base class. The first is
quite straightforward: You
simply add
brand
new functions to the derived
class. These new functions
are not
part
of the base class interface.
This means that the
base class simply
didn't
do as much as you wanted it
to, so you added more
functions. This
simple
and primitive use for
inheritance is, at times,
the perfect solution
to
your problem. However, you
should look closely for
the possibility that
your
base class might also
need these additional
functions. This process
of
discovery
and iteration of your design
happens regularly in
object-
oriented
programming.
Shape
draw()
erase()
move()
getColor()
setColor()
Circle
Square
Triangle
FlipVertical()
FlipHorizontal()
Although
inheritance may sometimes
imply (especially in Java,
where the
keyword
that indicates inheritance is
extends)
that you are going to
add
new
functions to the interface,
that's not necessarily true.
The second and
Chapter
1: Introduction to Objects
41
more
important way to differentiate
your new class is to
change
the
behavior
of an existing base-class function.
This is referred to as
overriding
that
function.
Shape
draw()
erase()
move()
getColor()
setColor()
Circle
Square
Triangle
draw()
draw()
draw()
erase()
erase()
erase()
To
override a function, you
simply create a new
definition for the
function
in
the derived class. You're
saying, "I'm using the
same interface
function
here,
but I want it to do something
different for my new
type."
Is-a
vs. is-like-a relationships
There's
a certain debate that can
occur about inheritance:
Should
inheritance
override only
base-class
functions (and not add
new member
functions
that aren't in the base
class)? This would mean
that the derived
type
is exactly
the
same type as the base
class since it has exactly
the same
interface.
As a result, you can exactly
substitute an object of the
derived
class
for an object of the base
class. This can be thought
of as pure
substitution,
and it's often referred to
as the substitution
principle. In a
sense,
this is the ideal way to
treat inheritance. We often
refer to the
relationship
between the base class
and derived classes in this
case as an
is-a
relationship,
because you can say "a
circle is
a shape."
A test for
inheritance
is to determine whether you
can state the is-a
relationship
about
the classes and have it
make sense.
42
Thinking
in Java
There
are times when you
must add new interface
elements to a derived
type,
thus extending the interface
and creating a new type.
The new type
can
still be substituted for the
base type, but the
substitution isn't
perfect
because
your new functions are
not accessible from the
base type. This
can
be described as an is-like-a5 relationship; the new
type has the
interface
of the old type but it
also contains other
functions, so you
can't
really
say it's exactly the
same. For example, consider
an air conditioner.
Suppose
your house is wired with
all the controls for
cooling; that is, it
has
an
interface that allows you to
control cooling. Imagine
that the air
conditioner
breaks down and you
replace it with a heat pump,
which can
both
heat and cool. The
heat pump is-like-an
air
conditioner, but it can
do
more.
Because the control system
of your house is designed
only to
control
cooling, it is restricted to
communication with the
cooling part of
the
new object. The interface of
the new object has
been extended, and
the
existing
system doesn't know about
anything except the original
interface.
Controls
Thermostat
Cooling
System
lowerTemperature()
cool()
Air
Conditioner
Heat
Pump
cool()
cool()
heat()
Of
course, once you see
this design it becomes clear
that the base
class
"cooling
system" is not general
enough, and should be
renamed to
"temperature
control system" so that it
can also include heating--at
which
point
the substitution principle
will work. However, the
diagram above is
an
example of what can happen
in design and in the real
world.
5
My
term.
Chapter
1: Introduction to Objects
43
When
you see the substitution
principle it's easy to feel
like this approach
(pure
substitution) is the only
way to do things, and in
fact it is
nice
if
your
design works out that
way. But you'll find
that there are times
when
it's
equally clear that you
must add new functions to
the interface of a
derived
class. With inspection both
cases should be reasonably
obvious.
Interchangeable
objects
with
polymorphism
When
dealing with type
hierarchies, you often want
to treat an object
not
as
the specific type that it
is, but instead as its
base type. This allows
you
to
write code that doesn't
depend on specific types. In
the shape example,
functions
manipulate generic shapes
without respect to whether
they're
circles,
squares, triangles, or some
shape that hasn't even
been defined
yet.
All shapes can be drawn,
erased, and moved, so these
functions
simply
send a message to a shape
object; they don't worry
about how the
object
copes with the
message.
Such
code is unaffected by the
addition of new types, and
adding new
types
is the most common way to
extend an object-oriented program
to
handle
new situations. For example,
you can derive a new
subtype of
shape
called pentagon without
modifying the functions that
deal only with
generic
shapes. This ability to
extend a program easily by
deriving new
subtypes
is important because it greatly
improves designs while
reducing
the
cost of software
maintenance.
There's
a problem, however, with
attempting to treat derived-type
objects
as
their generic base types
(circles as shapes, bicycles as
vehicles,
cormorants
as birds, etc.). If a function is
going to tell a generic
shape to
draw
itself, or a generic vehicle to
steer, or a generic bird to
move, the
compiler
cannot know at compile-time
precisely what piece of code
will be
executed.
That's the whole point--when
the message is sent,
the
programmer
doesn't want
to
know what piece of code
will be executed;
the
draw function can be applied
equally to a circle, a square, or a
triangle,
and
the object will execute
the proper code depending on
its specific type.
If
you don't have to know
what piece of code will be
executed, then when
you
add a new subtype, the
code it executes can be
different without
44
Thinking
in Java
requiring
changes to the function
call. Therefore, the
compiler cannot
know
precisely what piece of code
is executed, so what does it
do? For
example,
in the following diagram the
BirdController
object
just works
with
generic Bird
objects,
and does not know
what exact type they
are.
This
is convenient from BirdController's
perspective because it
doesn't
have
to write special code to
determine the exact type of
Bird it's
working
with,
or that Bird's
behavior. So how does it
happen that, when move(
)
is
called while ignoring the
specific type of Bird,
the right behavior
will
occur
(a Goose
runs,
flies, or swims, and a
Penguin
runs
or swims)?
BirdController
Bird
What
happens
reLocate()
move()
when
move() is
called?
Goose
Penguin
move()
move()
The
answer is the primary twist
in object-oriented programming:
the
compiler
cannot make a function call
in the traditional sense.
The
function
call generated by a non-OOP
compiler causes what is
called early
binding,
a term you may not
have heard before because
you've never
thought
about it any other way. It
means the compiler generates
a call to a
specific
function name, and the
linker resolves this call to
the absolute
address
of the code to be executed. In
OOP, the program
cannot
determine
the address of the code
until run-time, so some
other scheme is
necessary
when a message is sent to a
generic object.
To
solve the problem,
object-oriented languages use
the concept of late
binding.
When you send a message to
an object, the code being
called isn't
determined
until run-time. The compiler
does ensure that the
function
exists
and performs type checking
on the arguments and return
value (a
language
in which this isn't true is
called weakly
typed), but it
doesn't
know
the exact code to
execute.
Chapter
1: Introduction to Objects
45
To
perform late binding, Java
uses a special bit of code
in lieu of the
absolute
call. This code calculates
the address of the function
body, using
information
stored in the object (this
process is covered in great
detail in
Chapter
7). Thus, each object
can behave differently
according to the
contents
of that special bit of code.
When you send a message to
an object,
the
object actually does figure
out what to do with that
message.
In
some languages (C++, in
particular) you must
explicitly state that
you
want
a function to have the
flexibility of late-binding properties.
In these
languages,
by default, member functions
are not
dynamically
bound. This
caused
problems, so in Java dynamic
binding is the default and
you don't
need
to remember to add any extra
keywords in order to
get
polymorphism.
Consider
the shape example. The
family of classes (all based
on the same
uniform
interface) was diagrammed
earlier in this chapter.
To
demonstrate
polymorphism, we want to write a
single piece of code
that
ignores
the specific details of type
and talks only to the
base class. That
code
is decoupled
from
type-specific information, and
thus is simpler to
write
and easier to understand.
And, if a new type--a
Hexagon,
for
example--is
added through inheritance,
the code you write
will work just
as
well for the new
type of Shape
as
it did on the existing
types. Thus, the
program
is extensible.
If
you write a method in Java
(as you will soon
learn how to do):
void
doStuff(Shape s) {
s.erase();
//
...
s.draw();
}
This
function speaks to any
Shape,
so it is independent of the
specific
type
of object that it's drawing
and erasing. If in some
other part of the
program
we use the doStuff(
) function:
Circle
c = new Circle();
Triangle
t = new Triangle();
Line
l = new Line();
doStuff(c);
doStuff(t);
46
Thinking
in Java
doStuff(l);
The
calls to doStuff(
) automatically
work correctly, regardless of
the
exact
type of the object.
This
is actually a pretty amazing
trick. Consider the
line:
doStuff(c);
What's
happening here is that a
Circle
is
being passed into a
function
that's
expecting a Shape.
Since a Circle
is a
Shape
it
can be treated as
one
by doStuff(
).
That is, any message
that doStuff(
) can
send to a
Shape,
a Circle
can
accept. So it is a completely safe
and logical thing to
do.
We
call this process of
treating a derived type as
though it were its
base
type
upcasting.
The name cast
is
used in the sense of casting
into a mold
and
the up
comes
from the way the
inheritance diagram is
typically
arranged,
with the base type at
the top and the
derived classes fanning
out
downward.
Thus, casting to a base type
is moving up the
inheritance
diagram:
"upcasting."
Shape
"Upcasting"
Circle
Square
Triangle
An
object-oriented program contains
some upcasting somewhere,
because
that's
how you decouple yourself
from knowing about the
exact type
you're
working with. Look at the
code in doStuff(
):
s.erase();
//
...
s.draw();
Notice
that it doesn't say "If
you're a Circle,
do this, if you're a Square,
do
that, etc." If you write
that kind of code, which
checks for all
the
Chapter
1: Introduction to Objects
47
possible
types that a Shape
can
actually be, it's messy
and you need to
change
it every time you add a
new kind of Shape.
Here, you just
say
"You're
a shape, I know you can
erase( )
and
draw( )
yourself,
do it, and
take
care of the details
correctly."
What's
impressive about the code in
doStuff(
) is
that, somehow, the
right
thing happens. Calling
draw( )
for
Circle
causes
different code to
be
executed than when calling
draw( )
for
a Square
or
a Line,
but when
the
draw( )
message
is sent to an anonymous Shape,
the correct
behavior
occurs based on the actual
type of the Shape.
This is amazing
because,
as mentioned earlier, when
the Java compiler is
compiling the
code
for doStuff(
),
it cannot know exactly what
types it is dealing
with.
So
ordinarily, you'd expect it to
end up calling the version
of erase(
) and
draw(
) for
the base class Shape,
and not for the
specific Circle,
Square,
or Line.
And yet the right
thing happens because
of
polymorphism.
The compiler and run-time
system handle the details;
all
you
need to know is that it
happens, and more important
how to design
with
it. When you send a
message to an object, the
object will do the
right
thing,
even when upcasting is
involved.
Abstract
base classes and
interfaces
Often
in a design, you want the
base class to present
only
an
interface for
its
derived classes. That is,
you don't want anyone to
actually create an
object
of the base class, only to
upcast to it so that its
interface can be
used.
This is accomplished by making
that class abstract
using
the
abstract
keyword.
If anyone tries to make an
object of an abstract
class,
the
compiler prevents them. This
is a tool to enforce a particular
design.
You
can also use the
abstract
keyword
to describe a method that
hasn't
been
implemented yet--as a stub
indicating "here is an interface
function
for
all types inherited from
this class, but at this
point I don't have
any
implementation
for it." An abstract
method
may be created only
inside
an
abstract
class.
When the class is inherited,
that method must be
implemented,
or the inheriting class
becomes abstract
as
well. Creating
an
abstract
method
allows you to put a method
in an interface without
48
Thinking
in Java
being
forced to provide a possibly
meaningless body of code for
that
method.
The
interface
keyword
takes the concept of an
abstract
class
one step
further
by preventing any function
definitions at all. The
interface
is
a
very
handy and commonly used
tool, as it provides the
perfect separation
of
interface and implementation. In
addition, you can combine
many
interfaces
together, if you wish,
whereas inheriting from
multiple regular
classes
or abstract classes is not
possible.
Object
landscapes and
lifetimes
Technically,
OOP is just about abstract
data typing, inheritance,
and
polymorphism,
but other issues can be at
least as important.
The
remainder
of this section will cover
these issues.
One
of the most important
factors is the way objects
are created and
destroyed.
Where is the data for an
object and how is the
lifetime of the
object
controlled? There are
different philosophies at work
here. C++
takes
the approach that control of
efficiency is the most
important issue,
so
it gives the programmer a
choice. For maximum run-time
speed, the
storage
and lifetime can be
determined while the program
is being
written,
by placing the objects on
the stack (these are
sometimes called
automatic
or
scoped
variables)
or in the static storage
area. This places a
priority
on the speed of storage
allocation and release, and
control of
these
can be very valuable in some
situations. However, you
sacrifice
flexibility
because you must know
the exact quantity,
lifetime, and type of
objects
while you're writing the
program. If you are trying
to solve a more
general
problem such as computer-aided
design, warehouse
management,
or
air-traffic control, this is
too restrictive.
The
second approach is to create
objects dynamically in a pool of
memory
called
the heap. In this approach,
you don't know until
run-time how
many
objects you need, what
their lifetime is, or what
their exact type
is.
Those
are determined at the spur
of the moment while the
program is
running.
If you need a new object,
you simply make it on the
heap at the
Chapter
1: Introduction to Objects
49
point
that you need it.
Because the storage is
managed dynamically, at
run-time,
the amount of time required
to allocate storage on the
heap is
significantly
longer than the time to
create storage on the stack.
(Creating
storage
on the stack is often a
single assembly instruction to
move the
stack
pointer down, and another to
move it back up.) The
dynamic
approach
makes the generally logical
assumption that objects tend
to be
complicated,
so the extra overhead of
finding storage and
releasing that
storage
will not have an important
impact on the creation of an
object. In
addition,
the greater flexibility is
essential to solve the
general
programming
problem.
Java
uses the second approach,
exclusively6.
Every time you want
to
create
an object, you use the
new keyword
to build a dynamic instance
of
that
object.
There's
another issue, however, and
that's the lifetime of an
object. With
languages
that allow objects to be
created on the stack, the
compiler
determines
how long the object
lasts and can automatically
destroy it.
However,
if you create it on the heap
the compiler has no
knowledge of its
lifetime.
In a language like C++, you
must determine
programmatically
when
to destroy the object, which
can lead to memory leaks if
you don't
do
it correctly (and this is a
common problem in C++
programs). Java
provides
a feature called a garbage
collector that automatically
discovers
when
an object is no longer in use
and destroys it. A garbage
collector is
much
more convenient because it
reduces the number of issues
that you
must
track and the code
you must write. More
important, the
garbage
collector
provides a much higher level
of insurance against the
insidious
problem
of memory leaks (which has
brought many a C++ project
to its
knees).
The
rest of this section looks
at additional factors concerning
object
lifetimes
and landscapes.
6
Primitive types, which you'll
learn about later, are a
special case.
50
Thinking
in Java
Collections
and iterators
If
you don't know how
many objects you're going to
need to solve a
particular
problem, or how long they
will last, you also
don't know how to
store
those objects. How can
you know how much
space to create for
those
objects? You can't, since
that information isn't known
until run-
time.
The
solution to most problems in
object-oriented design seems
flippant:
you
create another type of
object. The new type of
object that solves
this
particular
problem holds references to
other objects. Of course,
you can
do
the same thing with an
array, which is available in
most languages. But
there's
more. This new object,
generally called a container
(also
called a
collection,
but the Java library
uses that term in a
different sense so
this
book
will use "container"), will
expand itself whenever
necessary to
accommodate
everything you place inside
it. So you don't need to
know
how
many objects you're going to
hold in a container. Just
create a
container
object and let it take
care of the details.
Fortunately,
a good OOP language comes
with a set of containers as
part
of
the package. In C++, it's
part of the Standard C++
Library and is
sometimes
called the Standard Template
Library (STL). Object Pascal
has
containers
in its Visual Component
Library (VCL). Smalltalk has
a very
complete
set of containers. Java also
has containers in its
standard
library.
In some libraries, a generic
container is considered good
enough
for
all needs, and in others
(Java, for example) the
library has different
types
of containers for different
needs: a vector (called an
ArrayList
in
Java)
for consistent access to all
elements, and a linked list
for consistent
insertion
at all elements, for
example, so you can choose
the particular
type
that fits your needs.
Container libraries may also
include sets,
queues,
hash tables, trees, stacks,
etc.
All
containers have some way to
put things in and get
things out; there
are
usually
functions to add elements to a
container, and others to
fetch those
elements
back out. But fetching
elements can be more
problematic,
because
a single-selection function is
restrictive. What if you
want to
manipulate
or compare a set of elements in
the container instead of
just
one?
Chapter
1: Introduction to Objects
51
The
solution is an iterator, which is an
object whose job is to
select the
elements
within a container and
present them to the user of
the iterator.
As
a class, it also provides a
level of abstraction. This
abstraction can be
used
to separate the details of
the container from the
code that's accessing
that
container. The container,
via the iterator, is
abstracted to be simply a
sequence.
The iterator allows you to
traverse that sequence
without
worrying
about the underlying
structure--that is, whether
it's an
ArrayList,
a LinkedList,
a Stack,
or something else. This
gives you the
flexibility
to easily change the
underlying data structure
without
disturbing
the code in your program.
Java began (in version
1.0 and 1.1)
with
a standard iterator, called
Enumeration,
for all of its
container
classes.
Java 2 has added a much
more complete container
library that
contains
an iterator called Iterator
that
does more than the
older
Enumeration.
From
a design standpoint, all you
really want is a sequence
that can be
manipulated
to solve your problem. If a
single type of sequence
satisfied
all
of your needs, there'd be no
reason to have different
kinds. There are
two
reasons that you need a
choice of containers. First,
containers provide
different
types of interfaces and
external behavior. A stack
has a different
interface
and behavior than that of a
queue, which is different
from that of
a
set or a list. One of these
might provide a more
flexible solution to
your
problem
than the other. Second,
different containers have
different
efficiencies
for certain operations. The
best example is an ArrayList
and
a
LinkedList.
Both are simple sequences
that can have
identical
interfaces
and external behaviors. But
certain operations can
have
radically
different costs. Randomly
accessing elements in an ArrayList
is
a
constant-time operation; it takes
the same amount of time
regardless of
the
element you select. However,
in a LinkedList
it
is expensive to move
through
the list to randomly select
an element, and it takes
longer to find
an
element that is further down
the list. On the other
hand, if you want to
insert
an element in the middle of a
sequence, it's much cheaper
in a
LinkedList
than
in an ArrayList.
These and other operations
have
different
efficiencies depending on the
underlying structure of
the
sequence.
In the design phase, you
might start with a LinkedList
and,
when
tuning for performance,
change to an ArrayList.
Because of the
abstraction
via iterators, you can
change from one to the
other with
minimal
impact on your code.
52
Thinking
in Java
In
the end, remember that a
container is only a storage
cabinet to put
objects
in. If that cabinet solves
all of your needs, it
doesn't really matter
how
it is implemented (a basic concept
with most types of objects).
If
you're
working in a programming environment
that has built-in
overhead
due
to other factors, then the
cost difference between an
ArrayList
and
a
LinkedList
might
not matter. You might
need only one type of
sequence.
You
can even imagine the
"perfect" container abstraction,
which can
automatically
change its underlying
implementation according to the
way
it
is used.
The
singly rooted
hierarchy
One
of the issues in OOP that
has become especially
prominent since the
introduction
of C++ is whether all
classes should ultimately be
inherited
from
a single base class. In Java
(as with virtually all
other OOP
languages)
the answer is "yes" and
the name of this ultimate
base class is
simply
Object.
It turns out that the
benefits of the singly
rooted hierarchy
are
many.
All
objects in a singly rooted
hierarchy have an interface in
common, so
they
are all ultimately the
same type. The alternative
(provided by C++) is
that
you don't know that
everything is the same
fundamental type. From
a
backward-compatibility
standpoint this fits the
model of C better and
can
be
thought of as less restrictive,
but when you want to do
full-on object-
oriented
programming you must then
build your own hierarchy to
provide
the
same convenience that's
built into other OOP
languages. And in any
new
class library you acquire,
some other incompatible
interface will be
used.
It requires effort (and
possibly multiple inheritance) to
work the
new
interface into your design.
Is the extra "flexibility" of
C++ worth it? If
you
need it--if you have a
large investment in C--it's
quite valuable. If
you're
starting from scratch, other
alternatives such as Java
can often be
more
productive.
All
objects in a singly rooted
hierarchy (such as Java
provides) can be
guaranteed
to have certain functionality.
You know you can
perform
certain
basic operations on every
object in your system. A
singly rooted
hierarchy,
along with creating all
objects on the heap, greatly
simplifies
argument
passing (one of the more
complex topics in
C++).
Chapter
1: Introduction to Objects
53
A
singly rooted hierarchy
makes it much easier to
implement a garbage
collector
(which is conveniently built
into Java). The necessary
support
can
be installed in the base
class, and the garbage
collector can thus
send
the
appropriate messages to every
object in the system.
Without a singly
rooted
hierarchy and a system to
manipulate an object via a
reference, it is
difficult
to implement a garbage
collector.
Since
run-time type information is
guaranteed to be in all objects,
you'll
never
end up with an object whose
type you cannot determine.
This is
especially
important with system level
operations, such as
exception
handling,
and to allow greater
flexibility in programming.
Collection
libraries and support
for
easy
collection use
Because
a container is a tool that
you'll use frequently, it
makes sense to
have
a library of containers that
are built in a reusable
fashion, so you can
take
one off the shelf
and plug it into your
program. Java provides such
a
library,
which should satisfy most
needs.
Downcasting
vs. templates/generics
To
make these containers
reusable, they hold the
one universal type in
Java
that was previously
mentioned: Object.
The singly rooted
hierarchy
means
that everything is an Object,
so a container that holds
Objects
can
hold anything. This makes
containers easy to
reuse.
To
use such a container, you
simply add object references
to it, and later
ask
for them back. But,
since the container holds
only Objects,
when you
add
your object reference into
the container it is upcast to
Object,
thus
losing
its identity. When you
fetch it back, you get an
Object
reference,
and
not a reference to the type
that you put in. So
how do you turn it
back
into
something that has the
useful interface of the
object that you put
into
the
container?
Here,
the cast is used again,
but this time you're
not casting up the
inheritance
hierarchy to a more general
type, you cast down
the hierarchy
to
a more specific type. This
manner of casting is called
downcasting.
With
upcasting, you know, for
example, that a Circle
is
a type of Shape
54
Thinking
in Java
so
it's safe to upcast, but
you don't know that an
Object
is
necessarily a
Circle
or
a Shape
so
it's hardly safe to downcast
unless you know
that's
what
you're dealing with.
It's
not completely dangerous,
however, because if you
downcast to the
wrong
thing you'll get a run-time
error called an exception,
which
will be
described
shortly. When you fetch
object references from a
container,
though,
you must have some
way to remember exactly what
they are so
you
can perform a proper
downcast.
Downcasting
and the run-time checks
require extra time for
the running
program,
and extra effort from
the programmer. Wouldn't it
make sense
to
somehow create the container
so that it knows the types
that it holds,
eliminating
the need for the
downcast and a possible
mistake? The
solution
is parameterized types, which
are classes that the
compiler can
automatically
customize to work with
particular types. For
example, with
a
parameterized container, the
compiler could customize
that container so
that
it would accept only Shapes
and fetch only
Shapes.
Parameterized
types are an important part
of C++, partly because
C++
has
no singly rooted hierarchy. In
C++, the keyword that
implements
parameterized
types is "template." Java
currently has no
parameterized
types
since it is possible for it to
get by--however awkwardly--using
the
singly
rooted hierarchy. However, a
current proposal for
parameterized
types
uses a syntax that is
strikingly similar to C++
templates.
The
housekeeping dilemma: who
should
clean up?
Each
object requires resources in
order to exist, most notably
memory.
When
an object is no longer needed it
must be cleaned up so that
these
resources
are released for reuse. In
simple programming situations
the
question
of how an object is cleaned up
doesn't seem too
challenging: you
create
the object, use it for as
long as it's needed, and
then it should be
destroyed.
It's not hard, however, to
encounter situations in which
the
situation
is more complex.
Suppose,
for example, you are
designing a system to manage
air traffic for
an
airport. (The same model
might also work for
managing crates in a
Chapter
1: Introduction to Objects
55
warehouse,
or a video rental system, or a
kennel for boarding pets.)
At
first
it seems simple: make a
container to hold airplanes,
then create a
new
airplane and place it in the
container for each airplane
that enters the
air-traffic-control
zone. For cleanup, simply
delete the
appropriate
airplane
object when a plane leaves
the zone.
But
perhaps you have some
other system to record data
about the planes;
perhaps
data that doesn't require
such immediate attention as
the main
controller
function. Maybe it's a
record of the flight plans
of all the small
planes
that leave the airport. So
you have a second container
of small
planes,
and whenever you create a
plane object you also
put it in this
second
container if it's a small
plane. Then some background
process
performs
operations on the objects in
this container during idle
moments.
Now
the problem is more
difficult: how can you
possibly know when to
destroy
the objects? When you're
done with the object,
some other part of
the
system might not be.
This same problem can
arise in a number of
other
situations, and in programming
systems (such as C++) in
which you
must
explicitly delete an object
when you're done with it
this can become
quite
complex.
With
Java, the garbage collector
is designed to take care of
the problem of
releasing
the memory (although this
doesn't include other
aspects of
cleaning
up an object). The garbage
collector "knows" when an
object is
no
longer in use, and it then
automatically releases the
memory for that
object.
This (combined with the
fact that all objects
are inherited from
the
single
root class Object
and
that you can create
objects only one way,
on
the
heap) makes the process of
programming in Java much
simpler than
programming
in C++. You have far
fewer decisions to make and
hurdles
to
overcome.
Garbage
collectors vs. efficiency
and
flexibility
If
all this is such a good
idea, why didn't they do
the same thing in
C++?
Well
of course there's a price
you pay for all
this programming
convenience,
and that price is run-time
overhead. As mentioned before,
in
C++
you can create objects on
the stack, and in this
case they're
automatically
cleaned up (but you don't
have the flexibility of
creating as
56
Thinking
in Java
many
as you want at run-time).
Creating objects on the
stack is the most
efficient
way to allocate storage for
objects and to free that
storage.
Creating
objects on the heap can be
much more expensive.
Always
inheriting
from a base class and
making all function calls
polymorphic
also
exacts a small toll. But
the garbage collector is a
particular problem
because
you never quite know
when it's going to start up
or how long it
will
take. This means that
there's an inconsistency in the
rate of execution
of
a Java program, so you can't
use it in certain situations,
such as when
the
rate of execution of a program is
uniformly critical. (These
are
generally
called real time programs,
although not all real
time
programming
problems are this
stringent.)
The
designers of the C++
language, trying to woo C
programmers (and
most
successfully, at that), did
not want to add any
features to the
language
that would impact the
speed or the use of C++ in
any situation
where
programmers might otherwise
choose C. This goal was
realized, but
at
the price of greater
complexity when programming in
C++. Java is
simpler
than C++, but the
trade-off is in efficiency and
sometimes
applicability.
For a significant portion of
programming problems,
however,
Java is the superior
choice.
Exception
handling:
dealing
with errors
Ever
since the beginning of
programming languages, error
handling has
been
one of the most difficult
issues. Because it's so hard
to design a good
error
handling scheme, many
languages simply ignore the
issue, passing
the
problem on to library designers
who come up with halfway
measures
that
can work in many situations
but can easily be
circumvented,
generally
by just ignoring them. A
major problem with most
error
handling
schemes is that they rely on
programmer vigilance in
following
an
agreed-upon convention that is
not enforced by the
language. If the
programmer
is not vigilant--often the
case if they are in a
hurry--these
schemes
can easily be
forgotten.
Exception
handling wires error
handling directly into the
programming
language
and sometimes even the
operating system. An exception is
an
Chapter
1: Introduction to Objects
57
object
that is "thrown" from the
site of the error and
can be "caught" by an
appropriate
exception handler designed to
handle that particular type
of
error.
It's as if exception handling is a
different, parallel path of
execution
that
can be taken when things go
wrong. And because it uses a
separate
execution
path, it doesn't need to
interfere with your normally
executing
code.
This makes that code
simpler to write since you
aren't constantly
forced
to check for errors. In
addition, a thrown exception is
unlike an
error
value that's returned from a
function or a flag that's
set by a function
in
order to indicate an error
condition--these can be ignored.
An
exception
cannot be ignored, so it's
guaranteed to be dealt with at
some
point.
Finally, exceptions provide a
way to reliably recover from
a bad
situation.
Instead of just exiting you
are often able to set
things right and
restore
the execution of a program,
which produces much more
robust
programs.
Java's
exception handling stands
out among programming
languages,
because
in Java, exception handling
was wired in from the
beginning and
you're
forced to use it. If you
don't write your code to
properly handle
exceptions,
you'll get a compile-time
error message. This
guaranteed
consistency
makes error handling much
easier.
It's
worth noting that exception
handling isn't an object-oriented
feature,
although
in object-oriented languages the
exception is normally
represented
with an object. Exception
handling existed before
object-
oriented
languages.
Multithreading
A
fundamental concept in computer
programming is the idea of
handling
more
than one task at a time.
Many programming problems
require that
the
program be able to stop what
it's doing, deal with
some other
problem,
and then return to the
main process. The solution
has been
approached
in many ways. Initially,
programmers with
low-level
knowledge
of the machine wrote
interrupt service routines
and the
suspension
of the main process was
initiated through a
hardware
interrupt.
Although this worked well,
it was difficult and
nonportable, so
it
made moving a program to a
new type of machine slow
and expensive.
58
Thinking
in Java
Sometimes
interrupts are necessary for
handling time-critical tasks,
but
there's
a large class of problems in
which you're simply trying
to partition
the
problem into separately
running pieces so that the
whole program can
be
more responsive. Within a
program, these separately
running pieces
are
called threads, and the
general concept is called
multithreading.
A
common
example of multithreading is the
user interface. By
using
threads,
a user can press a button
and get a quick response
rather than
being
forced to wait until the
program finishes its current
task.
Ordinarily,
threads are just a way to
allocate the time of a
single
processor.
But if the operating system
supports multiple processors,
each
thread
can be assigned to a different
processor and they can
truly run in
parallel.
One of the convenient
features of multithreading at the
language
level
is that the programmer
doesn't need to worry about
whether there
are
many processors or just one.
The program is logically
divided into
threads
and if the machine has
more than one processor
then the program
runs
faster, without any special
adjustments.
All
this makes threading sound
pretty simple. There is a
catch: shared
resources.
If you have more than
one thread running that's
expecting to
access
the same resource you
have a problem. For example,
two processes
can't
simultaneously send information to a
printer. To solve the
problem,
resources
that can be shared, such as
the printer, must be locked
while
they
are being used. So a thread
locks a resource, completes
its task, and
then
releases the lock so that
someone else can use
the resource.
Java's
threading is built into the
language, which makes a
complicated
subject
much simpler. The threading
is supported on an object level,
so
one
thread of execution is represented by
one object. Java also
provides
limited
resource locking. It can
lock the memory of any
object (which is,
after
all, one kind of shared
resource) so that only one
thread can use it at
a
time. This is accomplished
with the synchronized
keyword.
Other
types
of resources must be locked
explicitly by the programmer,
typically
by
creating an object to represent
the lock that all
threads must check
before
accessing that
resource.
Chapter
1: Introduction to Objects
59
Persistence
When
you create an object, it
exists for as long as you
need it, but
under
no
circumstances does it exist
when the program terminates.
While this
makes
sense at first, there are
situations in which it would be
incredibly
useful
if an object could exist and
hold its information even
while the
program
wasn't running. Then the
next time you started
the program, the
object
would be there and it would
have the same information it
had the
previous
time the program was
running. Of course, you can
get a similar
effect
by writing the information to a
file or to a database, but in
the spirit
of
making everything an object it
would be quite convenient to be
able to
declare
an object persistent and
have all the details
taken care of for
you.
Java
provides support for
"lightweight persistence," which
means that you
can
easily store objects on disk
and later retrieve them.
The reason it's
"lightweight"
is that you're still forced
to make explicit calls to do
the
storage
and retrieval. In addition,
JavaSpaces (described in Chapter
15)
provide
for a kind of persistent
storage of objects. In some
future release
more
complete support for
persistence might
appear.
Java
and the Internet
If
Java is, in fact, yet
another computer programming
language, you may
question
why it is so important and
why it is being promoted as
a
revolutionary
step in computer programming.
The answer isn't
immediately
obvious if you're coming
from a traditional
programming
perspective.
Although Java is very useful
for solving traditional
stand-
alone
programming problems, it is also
important because it will
solve
programming
problems on the World Wide
Web.
What
is the Web?
The
Web can seem a bit of a
mystery at first, with all
this talk of
"surfing,"
"presence,"
and "home pages." There
has even been a growing
reaction
against
"Internet-mania," questioning the
economic value and outcome
of
such
a sweeping movement. It's
helpful to step back and
see what it really
is,
but to do this you must
understand client/server systems,
another
aspect
of computing that's full of
confusing issues.
60
Thinking
in Java
Client/Server
computing
The
primary idea of a client/server
system is that you have a
central
repository
of information--some kind of data,
often in a database--that
you
want to distribute on demand to
some set of people or
machines. A
key
to the client/server concept is
that the repository of
information is
centrally
located so that it can be
changed and so that those
changes will
propagate
out to the information
consumers. Taken together,
the
information
repository, the software
that distributes the
information, and
the
machine(s) where the
information and software
reside is called the
server.
The software that resides on
the remote machine,
communicates
with
the server, fetches the
information, processes it,
and then displays it
on
the remote machine is called
the client.
The
basic concept of client/server
computing, then, is not so
complicated.
The
problems arise because you
have a single server trying
to serve many
clients
at once. Generally, a database
management system is involved
so
the
designer "balances" the
layout of data into tables
for optimal use. In
addition,
systems often allow a client
to insert new information
into a
server.
This means you must
ensure that one client's
new data doesn't
walk
over another client's new
data, or that data isn't
lost in the process
of
adding
it to the database. (This is
called transaction processing.) As
client
software
changes, it must be built,
debugged, and installed on
the client
machines,
which turns out to be more
complicated and expensive
than
you
might think. It's especially
problematic to support multiple
types of
computers
and operating systems.
Finally, there's the
all-important
performance
issue: you might have
hundreds of clients making
requests
of
your server at any one
time, and so any small
delay is crucial. To
minimize
latency, programmers work
hard to offload processing
tasks,
often
to the client machine, but
sometimes to other machines at
the server
site,
using so-called middleware.
(Middleware
is also used to
improve
maintainability.)
The
simple idea of distributing
information to people has so
many layers
of
complexity in implementing it that
the whole problem can
seem
hopelessly
enigmatic. And yet it's
crucial: client/server
computing
accounts
for roughly half of all
programming activities. It's
responsible for
everything
from taking orders and
credit-card transactions to
the
distribution
of any kind of data--stock
market, scientific, government,
you
Chapter
1: Introduction to Objects
61
name
it. What we've come up
with in the past is
individual solutions to
individual
problems, inventing a new
solution each time. These
were hard
to
create and hard to use,
and the user had to
learn a new interface
for
each
one. The entire
client/server problem needs to be
solved in a big
way.
The
Web as a giant server
The
Web is actually one giant
client/server system. It's a
bit worse than
that,
since you have all
the servers and clients
coexisting on a single
network
at once. You don't need to
know that, since all
you care about is
connecting
to and interacting with one
server at a time (even
though you
might
be hopping around the world
in your search for the
correct server).
Initially
it was a simple one-way
process. You made a request
of a server
and
it handed you a file, which
your machine's browser
software (i.e., the
client)
would interpret by formatting
onto your local machine.
But in
short
order people began wanting
to do more than just deliver
pages from
a
server. They wanted full
client/server capability so that
the client could
feed
information back to the
server, for example, to do
database lookups
on
the server, to add new
information to the server, or to
place an order
(which
required more security than
the original systems
offered). These
are
the changes we've been
seeing in the development of
the Web.
The
Web browser was a big
step forward: the concept
that one piece of
information
could be displayed on any
type of computer without
change.
However,
browsers were still rather
primitive and rapidly bogged
down by
the
demands placed on them. They
weren't particularly interactive,
and
tended
to clog up both the server
and the Internet because
any time you
needed
to do something that required
programming you had to
send
information
back to the server to be
processed. It could take
many
seconds
or minutes to find out you
had misspelled something in
your
request.
Since the browser was
just a viewer it couldn't
perform even the
simplest
computing tasks. (On the
other hand, it was safe,
since it couldn't
execute
any programs on your local
machine that might contain
bugs or
viruses.)
To
solve this problem,
different approaches have
been taken. To begin
with,
graphics standards have been
enhanced to allow better
animation
and
video within browsers. The
remainder of the problem can
be solved
62
Thinking
in Java
only
by incorporating the ability to
run programs on the client
end, under
the
browser. This is called
client-side programming.
Client-side
programming
The
Web's initial server-browser
design provided for
interactive content,
but
the interactivity was
completely provided by the
server. The server
produced
static pages for the
client browser, which would
simply interpret
and
display them. Basic HTML
contains simple mechanisms
for data
gathering:
text-entry boxes, check
boxes, radio boxes, lists
and drop-down
lists,
as well as a button that can
only be programmed to reset
the data on
the
form or "submit" the data on
the form back to the
server. This
submission
passes through the Common
Gateway Interface
(CGI)
provided
on all Web servers. The
text within the submission
tells CGI
what
to do with it. The most
common action is to run a
program located
on
the server in a directory
that's typically called
"cgi-bin." (If you
watch
the
address window at the top of
your browser when you
push a button on
a
Web page, you can
sometimes see "cgi-bin"
within all the
gobbledygook
there.)
These programs can be
written in most languages.
Perl is a
common
choice because it is designed
for text manipulation and
is
interpreted,
so it can be installed on any
server regardless of processor
or
operating
system.
Many
powerful Web sites today
are built strictly on CGI,
and you can in
fact
do nearly anything with it.
However, Web sites built on
CGI programs
can
rapidly become overly
complicated to maintain, and
there is also the
problem
of response time. The
response of a CGI program depends
on
how
much data must be sent, as
well as the load on both
the server and
the
Internet. (On top of this,
starting a CGI program tends to be
slow.)
The
initial designers of the Web
did not foresee how
rapidly this
bandwidth
would be exhausted for the
kinds of applications
people
developed.
For example, any sort of
dynamic graphing is
nearly
impossible
to perform with consistency
because a GIF file must be
created
and
moved from the server to
the client for each
version of the graph.
And
you've
no doubt had direct
experience with something as
simple as
validating
the data on an input form.
You press the submit
button on a
page;
the data is shipped back to
the server; the server
starts a CGI
program
that discovers an error,
formats an HTML page
informing you of
Chapter
1: Introduction to Objects
63
the
error, and then sends
the page back to you;
you must then back up
a
page
and try again. Not
only is this slow, it's
inelegant.
The
solution is client-side programming.
Most machines that run
Web
browsers
are powerful engines capable
of doing vast work, and
with the
original
static HTML approach they
are sitting there, just
idly waiting for
the
server to dish up the next
page. Client-side programming
means that
the
Web browser is harnessed to do
whatever work it can, and
the result
for
the user is a much speedier
and more interactive
experience at your
Web
site.
The
problem with discussions of
client-side programming is that
they
aren't
very different from
discussions of programming in general.
The
parameters
are almost the same,
but the platform is
different: a Web
browser
is like a limited operating
system. In the end, you
must still
program,
and this accounts for
the dizzying array of
problems and
solutions
produced by client-side programming.
The rest of this
section
provides
an overview of the issues
and approaches in
client-side
programming.
Plug-ins
One
of the most significant
steps forward in client-side
programming is
the
development of the plug-in.
This is a way for a
programmer to add
new
functionality to the browser by
downloading a piece of code
that
plugs
itself into the appropriate
spot in the browser. It
tells the browser
"from
now on you can perform
this new activity." (You
need to download
the
plug-in only once.) Some
fast and powerful behavior
is added to
browsers
via plug-ins, but writing a
plug-in is not a trivial
task, and isn't
something
you'd want to do as part of
the process of building a
particular
site.
The value of the plug-in
for client-side programming is
that it allows
an
expert programmer to develop a
new language and add
that language
to
a browser without the
permission of the browser
manufacturer. Thus,
plug-ins
provide a "back door" that
allows the creation of new
client-side
programming
languages (although not all
languages are implemented
as
plug-ins).
64
Thinking
in Java
Scripting
languages
Plug-ins
resulted in an explosion of scripting
languages. With a
scripting
language
you embed the source
code for your client-side
program directly
into
the HTML page, and
the plug-in that interprets
that language is
automatically
activated while the HTML
page is being displayed.
Scripting
languages
tend to be reasonably easy to
understand and, because they
are
simply
text that is part of an HTML
page, they load very
quickly as part of
the
single server hit required
to procure that page. The
trade-off is that
your
code is exposed for everyone
to see (and steal).
Generally, however,
you
aren't doing amazingly
sophisticated things with
scripting languages
so
this is not too much of a
hardship.
This
points out that the
scripting languages used
inside Web browsers
are
really
intended to solve specific
types of problems, primarily
the creation
of
richer and more interactive
graphical user interfaces
(GUIs). However,
a
scripting language might
solve 80 percent of the
problems encountered
in
client-side programming. Your
problems might very well
fit completely
within
that 80 percent, and since
scripting languages can
allow easier and
faster
development, you should
probably consider a scripting
language
before
looking at a more involved
solution such as Java or
ActiveX
programming.
The
most commonly discussed
browser scripting languages
are JavaScript
(which
has nothing to do with Java;
it's named that way
just to grab some
of
Java's marketing momentum),
VBScript (which looks like
Visual
Basic),
and Tcl/Tk, which comes
from the popular
cross-platform GUI-
building
language. There are others
out there, and no doubt
more in
development.
JavaScript
is probably the most
commonly supported. It comes
built into
both
Netscape Navigator and the
Microsoft Internet Explorer
(IE). In
addition,
there are probably more
JavaScript books available
than there
are
for the other browser
languages, and some tools
automatically create
pages
using JavaScript. However, if
you're already fluent in
Visual Basic
or
Tcl/Tk, you'll be more
productive using those
scripting languages
rather
than learning a new one.
(You'll have your hands
full dealing with
the
Web issues already.)
Chapter
1: Introduction to Objects
65
Java
If
a scripting language can
solve 80 percent of the
client-side
programming
problems, what about the
other 20 percent--the
"really
hard
stuff?" The most popular
solution today is Java. Not
only is it a
powerful
programming language built to be
secure, cross-platform,
and
international,
but Java is being
continually extended to provide
language
features
and libraries that elegantly
handle problems that are
difficult in
traditional
programming languages, such as
multithreading, database
access,
network programming, and
distributed computing. Java
allows
client-side
programming via the
applet.
An
applet is a mini-program that
will run only under a
Web browser. The
applet
is downloaded automatically as part of a
Web page (just as,
for
example,
a graphic is automatically downloaded).
When the applet is
activated
it executes a program. This is
part of its beauty--it
provides you
with
a way to automatically distribute
the client software from
the server
at
the time the user
needs the client software,
and no sooner. The
user
gets
the latest version of the
client software without fail
and without
difficult
reinstallation. Because of the
way Java is designed,
the
programmer
needs to create only a
single program, and that
program
automatically
works with all computers
that have browsers with
built-in
Java
interpreters. (This safely
includes the vast majority
of machines.)
Since
Java is a full-fledged programming
language, you can do as
much
work
as possible on the client
before and after making
requests of the
server.
For example, you won't
need to send a request form
across the
Internet
to discover that you've
gotten a date or some other
parameter
wrong,
and your client computer
can quickly do the work of
plotting data
instead
of waiting for the server to
make a plot and ship a
graphic image
back
to you. Not only do you
get the immediate win of
speed and
responsiveness,
but the general network
traffic and load on servers
can be
reduced,
preventing the entire
Internet from slowing
down.
One
advantage a Java applet has
over a scripted program is
that it's in
compiled
form, so the source code
isn't available to the
client. On the
other
hand, a Java applet can be
decompiled without too much
trouble,
but
hiding your code is often
not an important issue. Two
other factors
can
be important. As you will
see later in this book, a
compiled Java
applet
can comprise many modules
and take multiple server
"hits"
66
Thinking
in Java
(accesses)
to download. (In Java 1.1
and higher this is minimized
by Java
archives,
called JAR files, that
allow all the required
modules to be
packaged
together and compressed for
a single download.) A
scripted
program
will just be integrated into
the Web page as part of
its text (and
will
generally be smaller and
reduce server hits). This
could be important
to
the responsiveness of your
Web site. Another factor is
the all-important
learning
curve. Regardless of what
you've heard, Java is not a
trivial
language
to learn. If you're a Visual
Basic programmer, moving
to
VBScript
will be your fastest
solution, and since it will
probably solve
most
typical client/server problems
you might be hard pressed to
justify
learning
Java. If you're experienced
with a scripting language
you will
certainly
benefit from looking at
JavaScript or VBScript
before
committing
to Java, since they might
fit your needs handily
and you'll be
more
productive sooner.
ActiveX
To
some degree, the competitor
to Java is Microsoft's ActiveX,
although it
takes
a completely different approach.
ActiveX was originally a
Windows-
only
solution, although it is now
being developed via an
independent
consortium
to become cross-platform. Effectively,
ActiveX says "if
your
program
connects to its environment
just so, it can be dropped
into a Web
page
and run under a browser
that supports ActiveX." (IE
directly
supports
ActiveX and Netscape does so
using a plug-in.) Thus,
ActiveX
does
not constrain you to a
particular language. If, for
example, you're
already
an experienced Windows programmer
using a language such
as
C++,
Visual Basic, or Borland's
Delphi, you can create
ActiveX
components
with almost no changes to
your programming
knowledge.
ActiveX
also provides a path for
the use of legacy code in
your Web pages.
Security
Automatically
downloading and running
programs across the Internet
can
sound
like a virus-builder's dream.
ActiveX especially brings up
the
thorny
issue of security in client-side
programming. If you click on a
Web
site,
you might automatically
download any number of
things along with
the
HTML page: GIF files,
script code, compiled Java
code, and ActiveX
components.
Some of these are benign;
GIF files can't do any
harm, and
scripting
languages are generally
limited in what they can
do. Java was
Chapter
1: Introduction to Objects
67
also
designed to run its applets
within a "sandbox" of safety,
which
prevents
it from writing to disk or
accessing memory outside the
sandbox.
ActiveX
is at the opposite end of
the spectrum. Programming
with
ActiveX
is like programming Windows--you
can do anything you want.
So
if
you click on a page that
downloads an ActiveX component,
that
component
might cause damage to the
files on your disk. Of
course,
programs
that you load onto
your computer that are
not restricted to
running
inside a Web browser can do
the same thing. Viruses
downloaded
from
Bulletin-Board Systems (BBSs)
have long been a problem,
but the
speed
of the Internet amplifies
the difficulty.
The
solution seems to be "digital
signatures," whereby code is
verified to
show
who the author is.
This is based on the idea
that a virus works
because
its creator can be
anonymous, so if you remove
the anonymity
individuals
will be forced to be responsible
for their actions. This
seems
like
a good plan because it
allows programs to be much
more functional,
and
I suspect it will eliminate
malicious mischief. If,
however, a program
has
an unintentional destructive bug it
will still cause
problems.
The
Java approach is to prevent
these problems from
occurring, via the
sandbox.
The Java interpreter that
lives on your local Web
browser
examines
the applet for any
untoward instructions as the
applet is being
loaded.
In particular, the applet
cannot write files to disk
or erase files
(one
of the mainstays of viruses).
Applets are generally
considered to be
safe,
and since this is essential
for reliable client/server
systems, any bugs
in
the Java language that
allow viruses are rapidly
repaired. (It's worth
noting
that the browser software
actually enforces these
security
restrictions,
and some browsers allow
you to select different
security
levels
to provide varying degrees of
access to your
system.)
You
might be skeptical of this
rather draconian restriction
against writing
files
to your local disk. For
example, you may want to
build a local
database
or save data for later
use offline. The initial
vision seemed to be
that
eventually everyone would
get online to do anything
important, but
that
was soon seen to be
impractical (although low-cost
"Internet
appliances"
might someday satisfy the
needs of a significant segment
of
users).
The solution is the "signed
applet" that uses public-key
encryption
to
verify that an applet does
indeed come from where it
claims it does. A
68
Thinking
in Java
signed
applet can still trash
your disk, but the
theory is that since you
can
now
hold the applet creator
accountable they won't do
vicious things. Java
provides
a framework for digital
signatures so that you will
eventually be
able
to allow an applet to step
outside the sandbox if
necessary.
Digital
signatures have missed an
important issue, which is
the speed that
people
move around on the Internet.
If you download a buggy
program
and
it does something untoward,
how long will it be before
you discover
the
damage? It could be days or
even weeks. By then, how
will you track
down
the program that's done
it? And what good
will it do you at
that
point?
Internet
vs. intranet
The
Web is the most general
solution to the client/server
problem, so it
makes
sense that you can
use the same technology to
solve a subset of the
problem,
in particular the classic
client/server problem within
a
company.
With traditional client/server
approaches you have the
problem
of
multiple types of client
computers, as well as the
difficulty of installing
new
client software, both of
which are handily solved
with Web browsers
and
client-side programming. When
Web technology is used for
an
information
network that is restricted to a
particular company, it is
referred
to as an intranet. Intranets provide
much greater security
than
the
Internet, since you can
physically control access to
the servers within
your
company. In terms of training, it
seems that once people
understand
the
general concept of a browser
it's much easier for
them to deal with
differences
in the way pages and
applets look, so the
learning curve for
new
kinds of systems seems to be
reduced.
The
security problem brings us to
one of the divisions that
seems to be
automatically
forming in the world of
client-side programming. If
your
program
is running on the Internet,
you don't know what
platform it will
be
working under, and you
want to be extra careful
that you don't
disseminate
buggy code. You need
something cross-platform and
secure,
like
a scripting language or
Java.
If
you're running on an intranet,
you might have a different
set of
constraints.
It's not uncommon that
your machines could all
be
Intel/Windows
platforms. On an intranet, you're
responsible for the
quality
of your own code and
can repair bugs when
they're discovered. In
Chapter
1: Introduction to Objects
69
addition,
you might already have a
body of legacy code that
you've been
using
in a more traditional client/server
approach, whereby you
must
physically
install client programs
every time you do an
upgrade. The time
wasted
in installing upgrades is the
most compelling reason to
move to
browsers,
because upgrades are
invisible and automatic. If
you are
involved
in such an intranet, the
most sensible approach to
take is the
shortest
path that allows you to
use your existing code
base, rather than
trying
to recode your programs in a
new language.
When
faced with this bewildering
array of solutions to the
client-side
programming
problem, the best plan of
attack is a cost-benefit
analysis.
Consider
the constraints of your
problem and what would be
the shortest
path
to your solution. Since
client-side programming is
still
programming,
it's always a good idea to
take the fastest
development
approach
for your particular
situation. This is an aggressive
stance to
prepare
for inevitable encounters
with the problems of
program
development.
Server-side
programming
This
whole discussion has ignored
the issue of server-side
programming.
What
happens when you make a
request of a server? Most of
the time the
request
is simply "send me this
file." Your browser then
interprets the file
in
some appropriate fashion: as an
HTML page, a graphic image,
a Java
applet,
a script program, etc. A
more complicated request to a
server
generally
involves a database transaction. A
common scenario involves
a
request
for a complex database
search, which the server
then formats into
an
HTML page and sends to
you as the result. (Of
course, if the client
has
more
intelligence via Java or a
scripting language, the raw
data can be
sent
and formatted at the client
end, which will be faster
and less load on
the
server.) Or you might want
to register your name in a
database when
you
join a group or place an
order, which will involve
changes to that
database.
These database requests must
be processed via some code
on
the
server side, which is
generally referred to as server-side
programming.
Traditionally,
server-side programming has
been performed using
Perl
and
CGI scripts, but more
sophisticated systems have
been appearing.
These
include Java-based Web
servers that allow you to
perform all your
server-side
programming in Java by writing
what are called
servlets.
Servlets
and their offspring, JSPs,
are two of the most
compelling reasons
70
Thinking
in Java
that
companies who develop Web
sites are moving to Java,
especially
because
they eliminate the problems
of dealing with differently
abled
browsers.
A
separate arena:
applications
Much
of the brouhaha over Java
has been over applets.
Java is actually a
general-purpose
programming language that
can solve any type
of
problem--at
least in theory. And as
pointed out previously,
there might be
more
effective ways to solve most
client/server problems. When
you move
out
of the applet arena (and
simultaneously release the
restrictions, such
as
the one against writing to
disk) you enter the
world of general-purpose
applications
that run standalone, without
a Web browser, just like
any
ordinary
program does. Here, Java's
strength is not only in its
portability,
but
also its programmability. As
you'll see throughout this
book, Java has
many
features that allow you to
create robust programs in a
shorter
period
than with previous
programming languages.
Be
aware that this is a mixed
blessing. You pay for
the improvements
through
slower execution speed
(although there is significant
work going
on
in this area--JDK 1.3, in
particular, introduces the
so-called "hotspot"
performance
improvements). Like any
language, Java has
built-in
limitations
that might make it
inappropriate to solve certain
types of
programming
problems. Java is a rapidly
evolving language, however,
and
as
each new release comes
out it becomes more and
more attractive for
solving
larger sets of
problems.
Analysis
and design
The
object-oriented paradigm is a new
and different way of
thinking
about
programming. Many folks have
trouble at first knowing how
to
approach
an OOP project. Once you
know that everything is
supposed to
be
an object, and as you learn
to think more in an object-oriented
style,
you
can begin to create "good"
designs that take advantage
of all the
benefits
that OOP has to
offer.
A
method
(often
called a methodology)
is a set of processes and
heuristics
used
to break down the complexity
of a programming problem.
Many
OOP
methods have been formulated
since the dawn of
object-oriented
Chapter
1: Introduction to Objects
71
programming.
This section will give
you a feel for what
you're trying to
accomplish
when using a method.
Especially
in OOP, methodology is a field of
many experiments, so it is
important
to understand what problem
the method is trying to
solve
before
you consider adopting one.
This is particularly true
with Java, in
which
the programming language is
intended to reduce the
complexity
(compared
to C) involved in expressing a program.
This may in fact
alleviate
the need for
ever-more-complex methodologies. Instead,
simple
methodologies
may suffice in Java for a
much larger class of
problems
than
you could handle using
simple methodologies with
procedural
languages.
It's
also important to realize
that the term "methodology"
is often too
grand
and promises too much.
Whatever you do now when
you design
and
write a program is a method. It
may be your own method,
and you
may
not be conscious of doing
it, but it is a process you
go through as you
create.
If it is an effective process, it may
need only a small tune-up
to
work
with Java. If you are
not satisfied with your
productivity and the
way
your
programs turn out, you
may want to consider
adopting a formal
method,
or choosing pieces from
among the many formal
methods.
While
you're going through the
development process, the
most important
issue
is this: Don't get lost.
It's easy to do. Most of
the analysis and
design
methods
are intended to solve the
largest of problems. Remember
that
most
projects don't fit into
that category, so you can
usually have
successful
analysis and design with a
relatively small subset of
what a
method
recommends7.
But some sort of process, no
matter how limited,
will
generally get you on your
way in a much better fashion
than simply
beginning
to code.
It's
also easy to get stuck, to
fall into "analysis
paralysis," where you
feel
like
you can't move forward
because you haven't nailed
down every little
detail
at the current stage.
Remember, no matter how much
analysis you
do,
there are some things
about a system that won't
reveal themselves
7
An excellent example of
this is UML
Distilled, 2nd edition, by Martin Fowler
(Addison-
Wesley
2000), which reduces the sometimes-overwhelming
UML process to a manageable
subset.
72
Thinking
in Java
until
design time, and more
things that won't reveal
themselves until
you're
coding, or not even until a
program is up and running.
Because of
this,
it's crucial to move fairly
quickly through analysis and
design, and to
implement
a test of the proposed
system.
This
point is worth emphasizing.
Because of the history we've
had with
procedural
languages, it is commendable that a
team will want to
proceed
carefully
and understand every minute
detail before moving to
design and
implementation.
Certainly, when creating a
DBMS, it pays to
understand
a
customer's needs thoroughly.
But a DBMS is in a class of
problems that
is
very well-posed and
well-understood; in many such
programs, the
database
structure is
the
problem to be tackled. The
class of programming
problem
discussed in this chapter is of
the "wild-card" (my term)
variety,
in
which the solution isn't
simply re-forming a well-known
solution, but
instead
involves one or more
"wild-card factors"--elements for
which
there
is no well-understood previous solution,
and for which research
is
necessary8.
Attempting to thoroughly analyze a
wild-card problem
before
moving
into design and
implementation results in analysis
paralysis
because
you don't have enough
information to solve this
kind of problem
during
the analysis phase. Solving
such a problem requires
iteration
through
the whole cycle, and
that requires risk-taking
behavior (which
makes
sense, because you're trying
to do something new and the
potential
rewards
are higher). It may seem
like the risk is compounded
by "rushing"
into
a preliminary implementation, but it
can instead reduce the
risk in a
wild-card
project because you're
finding out early whether a
particular
approach
to the problem is viable.
Product development is
risk
management.
It's
often proposed that you
"build one to throw away."
With OOP, you
may
still throw part
of
it away, but because code is
encapsulated into
classes,
during the first pass
you will inevitably produce
some useful class
designs
and develop some worthwhile
ideas about the system
design that
do
not need to be thrown away.
Thus, the first rapid
pass at a problem not
8
My rule of thumb
for estimating such projects: If
there's more than one
wild card, don't
even
try to plan how long it's
going to take or how much it
will cost until you've
created a
working
prototype. There are too
many degrees of
freedom.
Chapter
1: Introduction to Objects
73
Table of Contents:
|
|||||