ZeePedia

Introduction to Objects:The progress of abstraction, An object has an interface

<< Preface:Introduction
Everything is an Object:You manipulate objects with references, Your first Java program >>
img
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
img
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
img
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
img
). 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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
(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
img
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
img
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
img
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
img
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
img
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
img
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:
  1. Introduction to Objects:The progress of abstraction, An object has an interface
  2. Everything is an Object:You manipulate objects with references, Your first Java program
  3. Controlling Program Flow:Using Java operators, Execution control, true and false
  4. Initialization & Cleanup:Method overloading, Member initialization
  5. Hiding the Implementation:the library unit, Java access specifiers, Interface and implementation
  6. Reusing Classes:Composition syntax, Combining composition and inheritance
  7. Polymorphism:Upcasting revisited, The twist, Designing with inheritance
  8. Interfaces & Inner Classes:Extending an interface with inheritance, Inner class identifiers
  9. Holding Your Objects:Container disadvantage, List functionality, Map functionality
  10. Error Handling with Exceptions:Basic exceptions, Catching an exception
  11. The Java I/O System:The File class, Compression, Object serialization, Tokenizing input
  12. Run-time Type Identification:The need for RTTI, A class method extractor
  13. Creating Windows & Applets:Applet restrictions, Running applets from the command line
  14. Multiple Threads:Responsive user interfaces, Sharing limited resources, Runnable revisited
  15. Distributed Computing:Network programming, Servlets, CORBA, Enterprise JavaBeans
  16. A: Passing & Returning Objects:Aliasing, Making local copies, Cloning objects
  17. B: The Java Native Interface (JNI):Calling a native method, the JNIEnv argument
  18. Java Programming Guidelines:Design, Implementation
  19. Resources:Software, Books, My own list of books
  20. Index