Thinking in Java, 2 Edition, Bruce Eckel, President,: Release 11
Thinking in Java, 2 Edition, Bruce Eckel, President,: Release 11
Java
Second Edition
Bruce Eckel
President, MindView, Inc.
Comments from readers:
Much better than any other Java book I’ve seen. Make that “by an order of
magnitude”... very complete, with excellent right-to-the-point examples
and intelligent, not dumbed-down, explanations ... In contrast to many
other Java books I found it to be unusually mature, consistent,
intellectually honest, well-written and precise. IMHO, an ideal book for
studying Java. Anatoly Vorobey, Technion University, Haifa,
Israel
One of the absolutely best programming tutorials I’ve seen for any
language. Joakim Ziegler, FIX sysop
Thank you for your wonderful, wonderful book on Java. Dr. Gavin
Pillay, Registrar, King Edward VIII Hospital, South Africa
Thank you again for your awesome book. I was really floundering (being a
non-C programmer), but your book has brought me up to speed as fast as
I could read it. It’s really cool to be able to understand the underlying
principles and concepts from the start, rather than having to try to build
that conceptual model through trial and error. Hopefully I will be able to
attend your seminar in the not-too-distant future. Randall R. Hawley,
Automation Technician, Eli Lilly & Co.
The best computer book writing I have seen. Tom Holland
This is one of the best books I’ve read about a programming language…
The best book ever written on Java. Ravindra Pai, Oracle
Corporation, SUNOS product line
This is the best book on Java that I have ever found! You have done a
great job. Your depth is amazing. I will be purchasing the book when it is
published. I have been learning Java since October 96. I have read a few
books, and consider yours a “MUST READ.” These past few months we
have been focused on a product written entirely in Java. Your book has
helped solidify topics I was shaky on and has expanded my knowledge
base. I have even used some of your explanations as information in
interviewing contractors to help our team. I have found how much Java
knowledge they have by asking them about things I have learned from
reading your book (e.g., the difference between arrays and Vectors). Your
book is great! Steve Wilkinson, Senior Staff Specialist, MCI
Telecommunications
Great book. Best book on Java I have seen so far. Jeff Sinclair,
Software Engineer, Kestral Computing
Thank you for Thinking in Java. It’s time someone went beyond mere
language description to a thoughtful, penetrating analytic tutorial that
doesn’t kowtow to The Manufacturers. I’ve read almost all the others—
only yours and Patrick Winston’s have found a place in my heart. I’m
already recommending it to customers. Thanks again. Richard Brooks,
Java Consultant, Sun Professional Services, Dallas
Other books cover the WHAT of Java (describing the syntax and the
libraries) or the HOW of Java (practical programming examples).
Thinking in Java is the only book I know that explains the WHY of Java;
why it was designed the way it was, why it works the way it does, why it
sometimes doesn’t work, why it’s better than C++, why it’s not. Although
it also does a good job of teaching the what and how of the language,
Thinking in Java is definitely the thinking person’s choice in a Java book.
Robert S. Stephenson
Thanks for writing a great book. The more I read it the better I like it. My
students like it, too. Chuck Iverson
I just want to commend you for your work on Thinking in Java. It is
people like you that dignify the future of the Internet and I just want to
thank you for your effort. It is very much appreciated. Patrick Barrell,
Network Officer Mamco, QAF Mfg. Inc.
Most of the Java books out there are fine for a start, and most just have
beginning stuff and a lot of the same examples. Yours is by far the best
advanced thinking book I’ve seen. Please publish it soon! ... I also bought
Thinking in C++ just because I was so impressed with Thinking in Java.
George Laframboise, LightWorx Technology Consulting, Inc.
I wrote to you earlier about my favorable impressions regarding your
Thinking in C++ (a book that stands prominently on my shelf here at
work). And today I’ve been able to delve into Java with your e-book in my
virtual hand, and I must say (in my best Chevy Chase from Modern
Problems) “I like it!” Very informative and explanatory, without reading
like a dry textbook. You cover the most important yet the least covered
concepts of Java development: the whys. Sean Brady
Your examples are clear and easy to understand. You took care of many
important details of Java that can’t be found easily in the weak Java
documentation. And you don’t waste the reader’s time with the basic facts
a programmer already knows. Kai Engert, Innovative Software,
Germany
I’m a great fan of your Thinking in C++ and have recommended it to
associates. As I go through the electronic version of your Java book, I’m
finding that you’ve retained the same high level of writing. Thank you!
Peter R. Neuwald
VERY well-written Java book...I think you’ve done a GREAT job on it. As
the leader of a Chicago-area Java special interest group, I’ve favorably
mentioned your book and Web site several times at our recent meetings. I
would like to use Thinking in Java as the basis for a part of each monthly
SIG meeting, in which we review and discuss each chapter in succession.
Mark Ertes
I really appreciate your work and your book is good. I recommend it here
to our users and Ph.D. students. Hugues Leroy // Irisa-Inria Rennes
France, Head of Scientific Computing and Industrial Tranfert
OK, I’ve only read about 40 pages of Thinking in Java, but I’ve already
found it to be the most clearly written and presented programming book
I’ve come across...and I’m a writer, myself, so I am probably a little
critical. I have Thinking in C++ on order and can’t wait to crack it—I’m
fairly new to programming and am hitting learning curves head-on
everywhere. So this is just a quick note to say thanks for your excellent
work. I had begun to burn a little low on enthusiasm from slogging
through the mucky, murky prose of most computer books—even ones that
came with glowing recommendations. I feel a whole lot better now.
Glenn Becker, Educational Theatre Association
Thank you for making your wonderful book available. I have found it
immensely useful in finally understanding what I experienced as
confusing in Java and C++. Reading your book has been very satisfying.
Felix Bizaoui, Twin Oaks Industries, Louisa, Va.
I must congratulate you on an excellent book. I decided to have a look at
Thinking in Java based on my experience with Thinking in C++, and I
was not disappointed. Jaco van der Merwe, Software Specialist,
DataFusion Systems Ltd, Stellenbosch, South Africa
This has to be one of the best Java books I’ve seen. E.F. Pritchard,
Senior Software Engineer, Cambridge Animation Systems Ltd.,
United Kingdom
Your book makes all the other Java books I’ve read or flipped through
seem doubly useless and insulting. Brett g Porter, Senior
Programmer, Art & Logic
I have been reading your book for a week or two and compared to the
books I have read earlier on Java, your book seems to have given me a
great start. I have recommended this book to a lot of my friends and they
have rated it excellent. Please accept my congratulations for coming out
with an excellent book. Rama Krishna Bhupathi, Software
Engineer, TCSI Corporation, San Jose
Just wanted to say what a “brilliant” piece of work your book is. I’ve been
using it as a major reference for in-house Java work. I find that the table
of contents is just right for quickly locating the section that is required.
It’s also nice to see a book that is not just a rehash of the API nor treats
the programmer like a dummy. Grant Sayer, Java Components
Group Leader, Ceedata Systems Pty Ltd, Australia
Wow! A readable, in-depth Java book. There are a lot of poor (and
admittedly a couple of good) Java books out there, but from what I’ve
seen yours is definitely one of the best. John Root, Web Developer,
Department of Social Security, London
I’ve *just* started Thinking in Java. I expect it to be very good because I
really liked Thinking in C++ (which I read as an experienced C++
programmer, trying to stay ahead of the curve). I’m somewhat less
experienced in Java, but expect to be very satisfied. You are a wonderful
author. Kevin K. Lewis, Technologist, ObjectSpace, Inc.
I think it’s a great book. I learned all I know about Java from this book.
Thank you for making it available for free over the Internet. If you
wouldn’t have I’d know nothing about Java at all. But the best thing is
that your book isn’t a commercial brochure for Java. It also shows the bad
sides of Java. YOU have done a great job here. Frederik Fix, Belgium
I have been hooked to your books all the time. A couple of years ago, when
I wanted to start with C++, it was C++ Inside & Out which took me
around the fascinating world of C++. It helped me in getting better
opportunities in life. Now, in pursuit of more knowledge and when I
wanted to learn Java, I bumped into Thinking in Java—no doubts in my
mind as to whether I need some other book. Just fantastic. It is more like
rediscovering myself as I get along with the book. It is just a month since I
started with Java, and heartfelt thanks to you, I am understanding it
better now. Anand Kumar S., Software Engineer,
Computervision, India
Your book stands out as an excellent general introduction. Peter
Robinson, University of Cambridge Computer Laboratory
It’s by far the best material I have come across to help me learn Java and I
just want you to know how lucky I feel to have found it. THANKS! Chuck
Peterson, Product Leader, Internet Product Line, IVIS
International
The book is great. It’s the third book on Java I’ve started and I’m about
two-thirds of the way through it now. I plan to finish this one. I found out
about it because it is used in some internal classes at Lucent Technologies
and a friend told me the book was on the Net. Good work. Jerry Nowlin,
MTS, Lucent Technologies
Thank you for writing two great books (Thinking in C++, Thinking in
Java). You have helped me immensely in my progression to object
oriented programming. Donald Lawson, DCL Enterprises
Thank you for taking the time to write a really helpful book on Java. If
teaching makes you understand something, by now you must be pretty
pleased with yourself. Dominic Turner, GEAC Support
It’s the best Java book I have ever read—and I read some. Jean-Yves
MENGANT, Chief Software Architect NAT-SYSTEM, Paris,
France
Thinking in Java gives the best coverage and explanation. Very easy to
read, and I mean the code fragments as well. Ron Chan, Ph.D., Expert
Choice, Inc., Pittsburgh PA
Your book is great. I have read lots of programming books and your book
still adds insights to programming in my mind. Ningjian Wang,
Information System Engineer, The Vanguard Group
Thinking in Java is an excellent and readable book. I recommend it to all
my students. Dr. Paul Gorman, Department of Computer Science,
University of Otago, Dunedin, New Zealand
You make it possible for the proverbial free lunch to exist, not just a soup
kitchen type of lunch but a gourmet delight for those who appreciate good
software and books about it. Jose Suriol, Scylax Corporation
Thanks for the opportunity of watching this book grow into a masterpiece!
IT IS THE BEST book on the subject that I’ve read or browsed. Jeff
Lapchinsky, Programmer, Net Results Technologies
Your book is concise, accessible and a joy to read. Keith Ritchie, Java
Research & Development Team, KL Group Inc.
It truly is the best book I’ve read on Java! Daniel Eng
The best book I have seen on Java! Rich Hoffarth, Senior Architect,
West Group
Thank you for a wonderful book. I’m having a lot of fun going through the
chapters. Fred Trimble, Actium Corporation
You have mastered the art of slowly and successfully making us grasp the
details. You make learning VERY easy and satisfying. Thank you for a
truly wonderful tutorial. Rajesh Rau, Software Consultant
Thinking in Java rocks the free world! Miko O’Sullivan, President,
Idocs Inc.
About Thinking in C++:
Best Book! Winner of the
1995 Software Development Magazine Jolt Award!
Java
Second Edition
Bruce Eckel
President, MindView, Inc.
Prentice Hall
Upper Saddle River, New Jersey 07458
www.phptr.com
Library of Congress Cataloging-in-Publication Data
Eckel, Bruce.
Thinking in Java / Bruce Eckel.--2nd ed.
p. cm.
ISBN 0-13-027363-5
1. Java (Computer program language) I. Title.
QA76.73.J38E25 2000
005.13'3--dc21 00-037522
CIP
Editorial/Production Supervision: Nicholas Radhuber
Acquisitions Editor: Paul Petralia
Manufacturing Manager: Maura Goldstaub
Marketing Manager: Bryan Gambrel
Cover Design: Daniel Will-Harris
Interior Design: Daniel Will-Harris, www.will-harris.com
The information in this book is distributed on an “as is” basis, without warranty. While every precaution
has been taken in the preparation of this book, neither the author nor the publisher shall have any liability
to any person or entitle with respect to any liability, loss or damage caused or alleged to be caused directly
or indirectly by instructions contained in this book or by the computer software or hardware products
described herein.
All rights reserved. No part of this book may be reproduced, in any form or by any means, without
permission in writing from the publisher.
Prentice-Hall books are widely used by corporations and government agencies for training, marketing, and
resale. The publisher offers discounts on this book when ordered in bulk quantities. For more information,
contact the Corporate Sales Department at 800-382-3419, fax: 201-236-7141, email:
[email protected] or write: Corporate Sales Department, Prentice Hall PTR, One Lake Street,
Upper Saddle River, New Jersey 07458.
Java is a registered trademark of Sun Microsystems, Inc. Windows 95 and Windows NT are trademarks of
Microsoft Corporation. All other product names and company names mentioned herein are the property of
their respective owners.
Choosing an RuntimeException.................550
This book is a case in point. A majority of folks thought I was very bold or
a little crazy to put the entire thing up on the Web. “Why would anyone
buy it?” they asked. If I had been of a more conservative nature I wouldn’t
have done it, but I really didn’t want to write another computer book in
the same old way. I didn’t know what would happen but it turned out to
be the smartest thing I’ve ever done with a book.
For one thing, people started sending in corrections. This has been an
amazing process, because folks have looked into every nook and cranny
and caught both technical and grammatical errors, and I’ve been able to
eliminate bugs of all sorts that I know would have otherwise slipped
through. People have been simply terrific about this, very often saying
“Now, I don’t mean this in a critical way…” and then giving me a
collection of errors I’m sure I never would have found. I feel like this has
1
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.
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.
One of the places I see the greatest impact for this is on the Web. Network
programming has always been hard, and Java makes it easy (and the Java
language designers are working on making it even easier). Network
programming is how we talk to each other more effectively and cheaper
than we ever have with telephones (email alone has revolutionized many
Preface 3
businesses). As we talk to each other more, amazing things begin to
happen, possibly more amazing even than the promise of genetic
engineering.
Preface 5
function works (so that you can know how to properly create one). There
have been other movements and changes, including a rewrite of Chapter
1, and removal of some appendices and other material that I consider no
longer necessary for the printed book, but those are the bulk of them. In
general, I’ve tried to go over everything, remove from the 2nd edition what
is no longer necessary (but which still exists in the electronic first edition),
include changes, and improve everything I could. As the language
continues to change—albeit not quite at the same breakneck pace as
before—there will no doubt be further editions of this book.
For those of you who still can’t stand the size of the book, I do apologize.
Believe it or not, I have worked hard to keep it small. Despite the bulk, I
feel like there may be enough alternatives to satisfy you. For one thing,
the book is available electronically (from the Web site, and also on the CD
ROM that accompanies this book), so if you carry your laptop you can
carry the book on that with no extra weight. If you’re really into slimming
down, there are actually Palm Pilot versions of the book floating around.
(One person told me he would read the book in bed on his Palm with the
backlighting on to keep from annoying his wife. I can only hope that it
helps send him to slumberland.) If you need it on paper, I know of people
who print a chapter at a time and carry it in their briefcase to read on the
train.
Java 2
At this writing, the release of Sun’s Java Development Kit (JDK) 1.3 is
imminent, and the proposed changes for JDK 1.4 have been publicized.
Although these version numbers are still in the “ones,” the standard way
to refer to any version of the language that is JDK 1.2 or greater is to call it
“Java 2.” This indicates the very significant changes between “old Java”—
which had many warts that I complained about in the first edition of this
book—and this more modern and improved version of the language,
which has far fewer warts and many additions and nice designs.
This book is written for Java 2. I have the great luxury of getting rid of all
the old stuff and writing to only the new, improved language because the
old information still exists in the electronic 1st edition on the Web and on
the CD ROM (which is where you can go if you’re stuck using a pre-Java-2
version of the language). Also, because anyone can freely download the
There is a bit of a catch, however. JDK 1.3 has some improvements that
I’d really like to use, but the version of Java that is currently being
released for Linux is JDK 1.2.2. Linux (see www.Linux.org) is a very
important development in conjunction with Java, because it is fast
becoming the most important server platform out there—fast, reliable,
robust, secure, well-maintained, and free, a true revolution in the history
of computing (I don’t think we’ve ever seen all of those features in any
tool before). And Java has found a very important niche in server-side
programming in the form of Servlets, a technology that is a huge
improvement over the traditional CGI programming (this is covered in
the “Distributed Programming” chapter).
So although I would like to only use the very newest features, it’s critical
that everything compiles under Linux, and so when you unpack the source
code and compile it under that OS (with the latest JDK) you’ll discover
that everything will compile. However, you will find that I’ve put notes
about features in JDK 1.3 here and there.
The CD ROM
Another bonus with this edition is the CD ROM that is packaged in the
back of the book. I’ve resisted putting CD ROMs in the back of my books
in the past because I felt the extra charge for a few Kbytes of source code
on this enormous CD was not justified, preferring instead to allow people
to download such things from my Web site. However, you’ll soon see that
this CD ROM is different.
The CD does contain the source code from the book, but it also contains
the book in its entirety, in several electronic formats. My favorite of these
is the HTML format, because it is fast and fully indexed—you just click on
an entry in the index or table of contents and you’re immediately at that
portion of the book.
The bulk of the 300+ Megabytes of the CD, however, is a full multimedia
course called Thinking in C: Foundations for C++ & Java. I originally
commissioned Chuck Allison to create this seminar-on-CD ROM as a
Preface 7
stand-alone product, but decided to include it with the second editions of
both Thinking in C++ and Thinking in Java because of the consistent
experience of having people come to seminars without an adequate
background in C. The thinking apparently goes “I’m a smart programmer
and I don’t want to learn C, but rather C++ or Java, so I’ll just skip C and
go directly to C++/Java.” After arriving at the seminar, it slowly dawns on
folks that the prerequisite of understanding C syntax is there for a very
good reason. By including the CD ROM with the book, we can ensure that
everyone attends a seminar with adequate preparation.
The CD also allows the book to appeal to a wider audience. Even though
Chapter 3 (Controlling program flow) does cover the fundamentals of the
parts of Java that come from C, the CD is a gentler introduction, and
assumes even less about the student’s programming background than
does the book. It is my hope that by including the CD more people will be
able to be brought into the fold of Java programming.
Throughout, I’ll be taking the attitude that you want to build a model in
your head that allows you to develop a deep understanding of the
language; if you encounter a puzzle you’ll be able to feed it to your model
and deduce the answer.
Prerequisites
This book assumes that you have some programming familiarity: you
understand that a program is a collection of statements, the idea of a
subroutine/function/macro, control statements such as “if” and looping
constructs such as “while,” etc. However, you might have learned this in
many places, such as programming with a macro language or working
with a tool like Perl. As long as you’ve programmed to the point where you
feel comfortable with the basic ideas of programming, you’ll be able to
work through this book. Of course, the book will be easier for the C
programmers and more so for the C++ programmers, but don’t count
yourself out if you’re not experienced with those languages (but come
9
willing to work hard; also, the multimedia CD that accompanies this book
will bring you up to speed on the basic C syntax necessary to learn Java).
I’ll be introducing the concepts of object-oriented programming (OOP)
and Java’s basic control mechanisms, so you’ll be exposed to those, and
the first exercises will involve the basic control-flow statements.
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.
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.
1. Present the material one simple step at a time so that you can easily
digest each concept before moving on.
Introduction 11
3. Carefully sequence the presentation of features so that you aren’t
seeing something that you haven’t been exposed to. Of course, this
isn’t always possible; in those situations, a brief introductory
description is given.
4. Give you what I think is important for you to understand about the
language, rather than everything I know. I believe there is an
information importance hierarchy, and that there are some facts
that 95 percent of programmers will never need to know and that
just confuse people and adds to their perception of the complexity
of the language. To take an example from C, if you memorize the
operator precedence table (I never did), you can write clever code.
But if you need to think about it, it will also confuse the
reader/maintainer of that code. So forget about precedence, and
use parentheses when things aren’t clear.
5. Keep each section focused enough so that the lecture time—and the
time between exercise periods—is small. Not only does this keep
the audience’s minds more active and involved during a hands-on
seminar, but it gives the reader a greater sense of accomplishment.
6. Provide you with a solid foundation so that you can understand the
issues well enough to move on to more difficult coursework and
books.
Online documentation
The Java language and libraries from Sun Microsystems (a free download)
come with documentation in electronic form, readable using a Web
browser, and virtually every third party implementation of Java has this
or an equivalent documentation system. Almost all the books published
on Java have duplicated this documentation. So you either already have it
or you can download it, and unless necessary, this book will not repeat
that documentation because it’s usually much faster if you find the class
descriptions with your Web browser than if you look them up in a book
(and the on-line documentation is probably more up-to-date). This book
will provide extra descriptions of the classes only when it’s necessary to
supplement the documentation so you can understand a particular
example.
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.
Introduction 13
how to create an object; an introduction to primitive types
and arrays; scoping and the way objects are destroyed by the
garbage collector; how everything in Java is a new data type
(class) and how to create your own classes; functions,
arguments, and return values; name visibility and using
components from other libraries; the static keyword; and
comments and embedded documentation.
Chapter 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.
Introduction 15
abstract class taken to the extreme, since it allows you to
perform a variation on C++’s “multiple inheritance,” by
creating a class that can be upcast to more than one base type.
Introduction 17
for the creation of Rapid-Application Development (RAD)
program-building tools.
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).
Multimedia CD ROM
There are two multimedia CDs associated with this book. The first is
bound into the book itself: Thinking in C, described at the end of the
preface, which prepares you for the book by bringing you up to speed on
the necessary C syntax you need to be able to understand Java.
Introduction 19
A second Multimedia CD ROM is available, which is based on the contents
of the book. This CD ROM is a separate product and contains the entire
contents of the week-long “Hands-On Java” training seminar. This is
more than 15 hours of lectures that I have recorded, synchronized with
hundreds of slides of information. Because the seminar is based on this
book, it is an ideal accompaniment.
The CD ROM contains all the lectures (with the important exception of
personalized attention!) from the five-day full-immersion training
seminars. We believe that it sets a new standard for quality.
Source code
All the source code for this book is available as copyrighted freeware,
distributed as a single package, by visiting the Web site
www.BruceEckel.com. 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 www.BruceEckel.com), 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
Introduction 21
You may use the code in your projects and in the classroom (including
your presentation materials) as long as the copyright notice that appears
in each source file is retained.
Coding standards
In the text of this book, identifiers (function, variable, and class names)
are set in bold. Most keywords are also set in bold, except for those
keywords that are used so much that the bolding can become tedious,
such as “class.”
I use a particular coding style for the examples in this book. This style
follows the style that Sun itself uses in virtually all of the code you will
find at its site (see java.sun.com/docs/codeconv/index.html), and seems
to be supported by most Java development environments. If you’ve read
my other works, you’ll also notice that Sun’s coding style coincides with
mine—this pleases me, although I had nothing to do with it. The subject of
formatting style is good for hours of hot debate, so I’ll just say I’m not
trying to dictate correct style via my examples; I have my own motivation
for using the style that I do. Because Java is a free-form programming
language, you can continue to use whatever style you’re comfortable with.
The programs in this book are files that are included by the word
processor in the text, directly from compiled files. Thus, the code files
printed in the book should all work without compiler errors. The errors
that should cause compile-time error messages are commented out with
the comment //! so they can be easily discovered and tested using
automatic means. Errors discovered and reported to the author will
appear first in the distributed source code and later in updates of the book
(which will also appear on the Web site www.BruceEckel.com).
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
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 www.BruceEckel.com 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.
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.
Introduction 23
anything you believe to be an error, please use this form to submit the
error along with your suggested correction. If necessary, include the
original source file and note any suggested modifications. Your help is
appreciated.
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).
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.
Introduction 25
Thanks to people who have spoken in my Java track at the Software
Development Conference, and students in my seminars, who ask the
questions I need to hear in order to make the material more clear.
Special thanks to Larry and Tina O’Brien, who helped turn my seminar
into the original Hands-On Java CD ROM. (You can find out more at
www.BruceEckel.com.)
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).
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
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.
The supporting cast of friends includes, but is not limited to: Andrew
Binstock, Steve Sinofsky, JD Hildebrandt, Tom Keffer, Brian McElhinney,
Brinkley Barr, Bill Gates at Midnight Engineering Magazine, Larry
Constantine and Lucy Lockwood, Greg Perry, Dan Putterman, Christi
Westphal, Gene Wang, Dave Mayer, David Intersimone, Andrea
Rosenfield, Claire Sawyers, more Italians (Laura Fallai, Corrado, Ilsa, and
Cristina Giustozzi), Chris and Laura Strand, the Almquists, Brad Jerbic,
Marilyn Cvitanic, the Mabrys, the Haflingers, the Pollocks, Peter Vinci,
the Robbins Families, the Moelter Families (and the McMillans), Michael
Wilk, Dave Stoner, Laurie Adams, the Cranstons, Larry Fogg, Mike and
Karen Sequeira, Gary Entsminger and Allison Brody, Kevin Donovan and
Sonda Eastlack, Chester and Shannon Andersen, Joe Lordi, Dave and
Introduction 27
Brenda Bartlett, David Lee, the Rentschlers, the Sudeks, Dick, Patty, and
Lee Eckel, Lynn and Todd, and their families. And of course, Mom and
Dad.
Internet contributors
Thanks to those who helped me rewrite the examples to use the Swing
library, and for other assistance: Jon Shvarts, Thomas Kirsch, Rahim
Adatia, Rajesh Jain, Ravi Manthena, Banu Rajamani, Jens Brandt, Nitin
Shivaram, Malcolm Davis, and everyone who expressed support. This
really helped me jump-start the project.
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
www.BruceEckel.com.
29
here eventually to fill in your knowledge so you can understand why
objects are important and how to design with them.
The progress of
abstraction
All programming languages provide abstractions. It can be argued that
the complexity of the problems you’re able to solve is directly related to
the kind and quality of abstraction. By “kind” I mean, “What is it that you
are abstracting?” Assembly language is a small abstraction of the
underlying machine. Many so-called “imperative” languages that followed
(such as Fortran, BASIC, and C) were abstractions of assembly language.
These languages are big improvements over assembly language, but their
primary abstraction still requires you to think in terms of the structure of
the computer rather than the structure of the problem you are trying to
solve. The programmer must establish the association between the
machine model (in the “solution space,” which is the place where you’re
modeling that problem, such as a computer) and the model of the
problem that is actually being solved (in the “problem space,” which is the
place where the problem exists). The effort required to perform this
mapping, and the fact that it is extrinsic to the programming language,
produces programs that are difficult to write and expensive to maintain,
and as a side effect created the entire “programming methods” industry.
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
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.
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()
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
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.
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
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)
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.
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.
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.
Shape
draw()
erase()
move()
getColor()
setColor()
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.
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()
FlipVertical()
FlipHorizontal()
Shape
draw()
erase()
move()
getColor()
setColor()
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.”
lowerTemperature() cool()
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.
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.
BirdController Bird
What happens
reLocate() when move() is move()
called?
Goose Penguin
move() move()
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);
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"
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
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.
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.
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).
6 Primitive types, which you’ll learn about later, are a special case.
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?
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.
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
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.
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
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.
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.
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.
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 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.
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.)
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
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).
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.
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”
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
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.)
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?
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
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.
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.
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.
1. What are the objects? (How do you partition your project into its
component parts?)
If you come up with nothing more than the objects and their interfaces,
then you can write a program. For various reasons you might need more
descriptions and documents than this, but you can’t get away with any
less.
The process can be undertaken in five phases, and a Phase 0 that is just
the initial commitment to using some kind of structure.
You might also decide at this phase that some additional process structure
is necessary, but not the whole nine yards. Understandably, some
programmers like to work in “vacation mode,” in which no structure is
imposed on the process of developing their work; “It will be done when
it’s done.” This can be appealing for a while, but I’ve found that having a
few milestones along the way helps to focus and galvanize your efforts
around those milestones instead of being stuck with the single goal of
“finish the project.” In addition, it divides the project into more bite-sized
pieces and makes it seem less threatening (plus the milestones offer more
opportunities for celebration).
The high concept is quite important because it sets the tone for your
project; it’s a mission statement. You won’t necessarily get it right the first
time (you may be in a later phase of the project before it becomes
completely clear), but keep trying until it feels right. For example, in an
air-traffic control system you may start out with a high concept focused on
the system that you’re building: “The tower program keeps track of the
aircraft.” But consider what happens when you shrink the system to a very
small airfield; perhaps there’s only a human controller, or none at all. A
more useful model won’t concern the solution you’re creating as much as
it describes the problem: “Aircraft arrive, unload, service and reload, then
depart.”
If you are designing an auto-teller, for example, the use case for a
particular aspect of the functionality of the system is able to describe what
the auto-teller does in every possible situation. Each of these “situations”
Use case diagrams are intentionally simple to prevent you from getting
bogged down in system implementation details prematurely:
Bank
Make
Deposit
Uses
Make Teller
Withdrawal
Get Account
Customer
Balance
Transfer
Between
Accounts
ATM
A use case does not need to be terribly complex, even if the underlying
system is complex. It is only intended to show the system as it appears to
the user. For example:
If you do get stuck, you can kick-start this phase by using a rough
approximation tool: describe the system in a few paragraphs and then
look for nouns and verbs. The nouns can suggest actors, context of the use
case (e.g., “lobby”), or artifacts manipulated in the use case. Verbs can
suggest interactions between actors and use cases, and specify steps
within the use case. You’ll also discover that nouns and verbs produce
objects and messages during the design phase (and note that use cases
describe interactions between subsystems, so the “noun and verb”
technique can be used only as a brainstorming tool as it does not generate
use cases) 10.
The boundary between a use case and an actor can point out the existence
of a user interface, but it does not define such a user interface. For a
process of defining and creating user interfaces, see Software for Use by
10 More information on use cases can be found in Applying Use Cases by Schneider &
Winters (Addison-Wesley 1998) and Use Case Driven Object Modeling with UML by
Rosenberg (Addison-Wesley 1999).
Although it’s a black art, at this point some kind of basic scheduling is
important. You now have an overview of what you’re building, so you’ll
probably be able to get some idea of how long it will take. A lot of factors
come into play here. If you estimate a long schedule then the company
might decide not to build it (and thus use their resources on something
more reasonable—that’s a good thing). Or a manager might have already
decided how long the project should take and will try to influence your
estimate. But it’s best to have an honest schedule from the beginning and
deal with the tough decisions early. There have been a lot of attempts to
come up with accurate scheduling techniques (much like techniques to
predict the stock market), but probably the best approach is to rely on
your experience and intuition. Get a gut feeling for how long it will really
take, then double that and add 10 percent. Your gut feeling is probably
correct; you can get something working in that time. The “doubling” will
turn that into something decent, and the 10 percent will deal with the
final polishing and details11. However you want to explain it, and
regardless of the moans and manipulations that happen when you reveal
such a schedule, it just seems to work out that way.
1. The name of the class. It’s important that this name capture the
essence of what the class does, so that it makes sense at a glance.
11 My personal take on this has changed lately. Doubling and adding 10 percent will give
you a reasonably accurate estimate (assuming there are not too many wild-card factors),
but you still have to work quite diligently to finish in that time. If you want time to really
make it elegant and to enjoy yourself in the process, the correct multiplier is more like
three or four times, I believe.
You may feel like the cards should be bigger because of all the information
you’d like to get on them, but they are intentionally small, not only to keep
your classes small but also to keep you from getting into too much detail
too early. If you can’t fit all you need to know about a class on a small
card, the class is too complex (either you’re getting too detailed, or you
should create more than one class). The ideal class should be understood
at a glance. The idea of CRC cards is to assist you in coming up with a first
cut of the design so that you can get the big picture and then refine your
design.
One of the great benefits of CRC cards is in communication. It’s best done
real time, in a group, without computers. Each person takes responsibility
for several classes (which at first have no names or other information).
You run a live simulation by solving one scenario at a time, deciding
which messages are sent to the various objects to satisfy each scenario. As
you go through this process, you discover the classes that you need along
with their responsibilities and collaborations, and you fill out the cards as
you do this. When you’ve moved through all the use cases, you should
have a fairly complete first cut of your design.
Once you’ve come up with a set of CRC cards, you may want to create a
more formal description of your design using UML12. You don’t need to
use UML, but it can be helpful, especially if you want to put up a diagram
on the wall for everyone to ponder, which is a good idea. An alternative to
UML is a textual description of the objects and their interfaces, or,
depending on your programming language, the code itself13.
You’ll know you’re done with Phase 2 when you have described the objects
and their interfaces. Well, most of them—there are usually a few that slip
through the cracks and don’t make themselves known until Phase 3. But
that’s OK. All you are concerned with is that you eventually discover all of
your objects. It’s nice to discover them early in the process, but OOP
1. Let a specific problem generate a class, then let the class grow and
mature during the solution of other problems.
5. Always keep it simple. Little clean objects with obvious utility are
better than big complicated interfaces. When decision points come
up, use an Occam’s Razor approach: Consider the choices and
select the one that is simplest, because simple classes are almost
always best. Start small and simple, and you can expand the class
interface when you understand it better. As time goes on, it’s
difficult to remove elements from a class.
Your goal is to find the core of your system architecture that needs to be
implemented in order to generate a running system, no matter how
incomplete that system is in this initial pass. You’re creating a framework
that you can build on with further iterations. You’re also performing the
first of many system integrations and tests, and giving the stakeholders
feedback about what their system will look like and how it is progressing.
Ideally, you are also exposing some of the critical risks. You’ll probably
also discover changes and improvements that can be made to your
original architecture—things you would not have learned without
implementing the system.
Part of building the system is the reality check that you get from testing
against your requirements analysis and system specification (in whatever
form they exist). Make sure that your tests verify the requirements and
use cases. When the core of the system is stable, you’re ready to move on
and add more functionality.
How big is an iteration? Ideally, each iteration lasts one to three weeks
(this can vary based on the implementation language). At the end of that
period, you have an integrated, tested system with more functionality
than it had before. But what’s particularly interesting is the basis for the
iteration: a single use case. Each use case is a package of related
functionality that you build into the system all at once, during one
iteration. Not only does this give you a better idea of what the scope of a
use case should be, but it also gives more validation to the idea of a use
case, since the concept isn’t discarded after analysis and design, but
instead it is a fundamental unit of development throughout the software-
building process.
Phase 5: Evolution
This is the point in the development cycle that has traditionally been
called “maintenance,” a catch-all term that can mean everything from
“getting it to work the way it was really supposed to in the first place” to
“adding features that the customer forgot to mention” to the more
traditional “fixing the bugs that show up” and “adding new features as the
need arises.” So many misconceptions have been applied to the term
“maintenance” that it has taken on a slightly deceiving quality, partly
because it suggests that you’ve actually built a pristine program and all
you need to do is change parts, oil it, and keep it from rusting. Perhaps
there’s a better term to describe what’s going on.
I’ll use the term evolution14. That is, “You won’t get it right the first time,
so give yourself the latitude to learn and to go back and make changes.”
You might need to make a lot of changes as you learn and understand the
problem more deeply. The elegance you’ll produce if you evolve until you
get it right will pay off, both in the short and the long term. Evolution is
What it means to “get it right” isn’t just that the program works according
to the requirements and the use cases. It also means that the internal
structure of the code makes sense to you, and feels like it fits together
well, with no awkward syntax, oversized objects, or ungainly exposed bits
of code. In addition, you must have some sense that the program
structure will survive the changes that it will inevitably go through during
its lifetime, and that those changes can be made easily and cleanly. This is
no small feat. You must not only understand what you’re building, but
also how the program will evolve (what I call the vector of change).
Fortunately, object-oriented programming languages are particularly
adept at supporting this kind of continuing modification—the boundaries
created by the objects are what tend to keep the structure from breaking
down. They also allow you to make changes—ones that would seem
drastic in a procedural program—without causing earthquakes
throughout your code. In fact, support for evolution might be the most
important benefit of OOP.
With evolution, you create something that at least approximates what you
think you’re building, and then you kick the tires, compare it to your
requirements, and see where it falls short. Then you can go back and fix it
by redesigning and reimplementing the portions of the program that
didn’t work right15. You might actually need to solve the problem, or an
aspect of the problem, several times before you hit on the right solution.
(A study of Design Patterns is usually helpful here. You can find
information in Thinking in Patterns with Java, downloadable at
www.BruceEckel.com.)
15 This is something like “rapid prototyping,” where you were supposed to build a quick-
and-dirty version so that you could learn about the system, and then throw away your
prototype and build it right. The trouble with rapid prototyping is that people didn’t throw
away the prototype, but instead built upon it. Combined with the lack of structure in
procedural programming, this often leads to messy systems that are expensive to
maintain.
Extreme programming
I have studied analysis and design techniques, on and off, since I was in
graduate school. The concept of Extreme Programming (XP) is the most
radical, and delightful, that I’ve seen. You can find it chronicled in
Extreme Programming Explained by Kent Beck (Addison-Wesley, 2000)
and on the Web at www.xprogramming.com.
While creating the tests, you are forced to completely think out the class
and will often discover needed functionality that might be missed during
the thought experiments of UML diagrams, CRC cards, use cases, etc.
The second important effect of writing the tests first comes from running
the tests every time you do a build of your software. This activity gives you
the other half of the testing that’s performed by the compiler. If you look
at the evolution of programming languages from this perspective, you’ll
see that the real improvements in the technology have actually revolved
around testing. Assembly language checked only for syntax, but C
imposed some semantic restrictions, and these prevented you from
making certain types of mistakes. OOP languages impose even more
semantic restrictions, which if you think about it are actually forms of
testing. “Is this data type being used properly?” and “Is this function being
called properly?” are the kinds of tests that are being performed by the
compiler or run-time system. We’ve seen the results of having these tests
built into the language: people have been able to write more complex
systems, and get them to work, with much less time and effort. I’ve
puzzled over why this is, but now I realize it’s the tests: you do something
wrong, and the safety net of the built-in tests tells you there’s a problem
and points you to where it is.
But the built-in testing afforded by the design of the language can only go
so far. At some point, you must step in and add the rest of the tests that
produce a full suite (in cooperation with the compiler and run-time
system) that verifies all of your program. And, just like having a compiler
watching over your shoulder, wouldn’t you want these tests helping you
One of the things that I’ve discovered about the use of more and more
powerful programming languages is that I am emboldened to try more
brazen experiments, because I know that the language will keep me from
wasting my time chasing bugs. The XP test scheme does the same thing
for your entire project. Because you know your tests will always catch any
problems that you introduce (and you regularly add any new tests as you
think of them), you can make big changes when you need to without
worrying that you’ll throw the whole project into complete disarray. This
is incredibly powerful.
Pair programming
Pair programming goes against the rugged individualism that we’ve been
indoctrinated into from the beginning, through school (where we succeed
or fail on our own, and working with our neighbors is considered
“cheating”), and media, especially Hollywood movies in which the hero is
usually fighting against mindless conformity16. Programmers, too, are
considered paragons of individuality—“cowboy coders” as Larry
Constantine likes to say. And yet XP, which is itself battling against
conventional thinking, says that code should be written with two people
per workstation. And that this should be done in an area with a group of
workstations, without the barriers that the facilities-design people are so
fond of. In fact, Beck says that the first task of converting to XP is to arrive
with screwdrivers and Allen wrenches and take apart everything that gets
in the way.17 (This will require a manager who can deflect the ire of the
facilities department.)
16 Although this may be a more American perspective, the stories of Hollywood reach
everywhere.
I’ve begun using pair programming during the exercise periods in some of
my seminars and it seems to significantly improve everyone’s experience.
Error handling
Error handling in C is a notorious problem, and one that is often
ignored—finger-crossing is usually involved. If you’re building a large,
complex program, there’s nothing worse than having an error buried
somewhere with no clue as to where it came from. Java exception
handling is a way to guarantee that an error is noticed, and that
something happens as a result.
Guidelines
Here are some guidelines to consider when making the transition to OOP
and Java:
1. Training
The first step is some form of education. Remember the company’s
investment in code, and try not to throw everything into disarray for six to
nine months while everyone puzzles over how interfaces work. Pick a
small group for indoctrination, preferably one composed of people who
are curious, work well together, and can function as their own support
network while they’re learning Java.
2. Low-risk project
Try a low-risk project first and allow for mistakes. Once you’ve gained
some experience, you can either seed other projects from members of this
first team or use the team members as an OOP technical support staff.
This first project may not work right the first time, so it should not be
mission-critical for the company. It should be simple, self-contained, and
instructive; this means that it should involve creating classes that will be
meaningful to the other programmers in the company when they get their
turn to learn Java.
Management obstacles
If you’re a manager, your job is to acquire resources for your team, to
overcome barriers to your team’s success, and in general to try to provide
the most productive and enjoyable environment so your team is most
likely to perform those miracles that are always being asked of you.
Moving to Java falls in all three of these categories, and it would be
wonderful if it didn’t cost you anything as well. Although moving to Java
may be cheaper—depending on your constraints—than the OOP
alternatives for a team of C programmers (and probably for programmers
in other procedural languages), it isn’t free, and there are obstacles you
should be aware of before trying to sell the move to Java within your
company and embarking on the move itself.
Startup costs
The cost of moving to Java is more than just the acquisition of Java
compilers (the Sun Java compiler is free, so this is hardly an obstacle).
Your medium- and long-term costs will be minimized if you invest in
training (and possibly mentoring for your first project) and also if you
identify and purchase class libraries that solve your problem rather than
trying to build those libraries yourself. These are hard-money costs that
must be factored into a realistic proposal. In addition, there are the
hidden costs in loss of productivity while learning a new language and
possibly a new programming environment. Training and mentoring can
certainly minimize these, but team members must overcome their own
struggles to understand the new technology. During this process they will
make more mistakes (this is a feature, because acknowledged mistakes
are the fastest path to learning) and be less productive. Even then, with
some types of programming problems, the right classes, and the right
development environment, it’s possible to be more productive while
you’re learning Java (even considering that you’re making more mistakes
and writing fewer lines of code per day) than if you’d stayed with C.
I’m beginning to think that the strength of Java lies in a slightly different
arena than that of C++. C++ is a language that doesn’t try to fit a mold.
Certainly it has been adapted in a number of ways to solve particular
problems. Some C++ tools combine libraries, component models, and
code-generation tools to solve the problem of developing windowed end-
user applications (for Microsoft Windows). And yet, what do the vast
majority of Windows developers use? Microsoft’s Visual Basic (VB). This
despite the fact that VB produces the kind of code that becomes
unmanageable when the program is only a few pages long (and syntax
that can be positively mystifying). As successful and popular as VB is, it’s
not a very good example of language design. It would be nice to have the
ease and power of VB without the resulting unmanageable code. And
that’s where I think Java will shine: as the “next VB.” You may or may not
shudder to hear this, but think about it: so much of Java is intended to
make it easy for the programmer to solve application-level problems like
networking and cross-platform UI, and yet it has a language design that
allows the creation of very large and flexible bodies of code. Add to this
the fact that Java has the most robust type checking and error handling
systems I’ve ever seen in a language and you have the makings of a
significant leap forward in programming productivity.
Should you use Java instead of C++ for your project? Other than Web
applets, there are two issues to consider. First, if you want to use a lot of
existing C++ libraries (and you’ll certainly get a lot of productivity gains
If you’re developing all your code primarily from scratch, then the
simplicity of Java over C++ will significantly shorten your development
time—the anecdotal evidence (stories from C++ teams that I’ve talked to
who have switched to Java) suggests a doubling of development speed
over C++. If Java performance doesn’t matter or you can somehow
compensate for it, sheer time-to-market issues make it difficult to choose
C++ over Java.
The biggest issue is performance. Interpreted Java has been slow, even 20
to 50 times slower than C in the original Java interpreters. This has
improved greatly over time, but it will still remain an important number.
Computers are about speed; if it wasn’t significantly faster to do
something on a computer then you’d do it by hand. (I’ve even heard it
suggested that you start with Java, to gain the short development time,
then use a tool and support libraries to translate your code to C++, if you
need faster execution speed.)
The key to making Java feasible for most development projects is the
appearance of speed improvements like so-called “just-in time” (JIT)
compilers, Sun’s own “hotspot” technology, and even native code
compilers. Of course, native code compilers will eliminate the touted
cross-platform execution of the compiled programs, but they will also
bring the speed of the executable closer to that of C and C++. And cross-
compiling a program in Java should be a lot easier than doing so in C or
C++. (In theory, you just recompile, but that promise has been made
before for other languages.)
You can find comparisons of Java and C++ and observations about Java
realities in the appendices of the first edition of this book (Available on
this book’s accompanying CD ROM, as well as at www.BruceEckel.com).
Summary
This chapter attempts to give you a feel for the broad issues of object-
oriented programming and Java, including why OOP is different, and why
Java in particular is different, concepts of OOP methodologies, and finally
OOP and Java may not be for everyone. It’s important to evaluate your
own needs and decide whether Java will optimally satisfy those needs, or
if you might be better off with another programming system (including
the one you’re currently using). If you know that your needs will be very
specialized for the foreseeable future and if you have specific constraints
that may not be satisfied by Java, then you owe it to yourself to investigate
the alternatives18. Even if you eventually choose Java as your language,
you’ll at least understand what the options were and have a clear vision of
why you took that direction.
You know what a procedural program looks like: data definitions and
function calls. To find the meaning of such a program you have to work a
little, looking through the function calls and low-level concepts to create a
model in your mind. This is the reason we need intermediate
representations when designing procedural programs—by themselves,
these programs tend to be confusing because the terms of expression are
oriented more toward the computer than to the problem you’re solving.
Because Java adds many new concepts on top of what you find in a
procedural language, your natural assumption may be that the main( ) in
a Java program will be far more complicated than for the equivalent C
program. Here, you’ll be pleasantly surprised: A well-written Java
program is generally far simpler and much easier to understand than the
equivalent C program. What you’ll see are the definitions of the objects
that represent concepts in your problem space (rather than the issues of
the computer representation) and messages sent to those objects to
represent the activities in that space. One of the delights of object-
oriented programming is that, with a well-designed program, it’s easy to
understand the code by reading it. Usually there’s a lot less code as well,
because many of your problems will be solved by reusing existing library
code.
101
All this is simplified in Java. You treat everything as an object, so there is
a single consistent syntax that you use everywhere. Although you treat
everything as an object, the identifier you manipulate is actually a
“reference” to an object1. You might imagine this scene as a television (the
object) with your remote control (the reference). As long as you’re holding
this reference, you have a connection to the television, but when someone
says “change the channel” or “lower the volume,” what you’re
manipulating is the reference, which in turn modifies the object. If you
want to move around the room and still control the television, you take
the remote/reference with you, not the television.
Also, the remote control can stand on its own, with no television. That is,
just because you have a reference doesn’t mean there’s necessarily an
object connected to it. So if you want to hold a word or sentence, you
create a String reference:
String s;
But here you’ve created only the reference, not an object. If you decided to
send a message to s at this point, you’ll get an error (at run-time) because
s isn’t actually attached to anything (there’s no television). A safer
practice, then, is always to initialize a reference when you create it:
String s = "asdf";
1 This can be a flashpoint. There are those who say “clearly, it’s a pointer,” but this
presumes an underlying implementation. Also, Java references are much more akin to
C++ references than pointers in their syntax. In the first edition of this book, I chose to
invent a new term, “handle,” because C++ references and Java references have some
important differences. I was coming out of C++ and did not want to confuse the C++
programmers whom I assumed would be the largest audience for Java. In the 2nd edition, I
decided that “reference” was the more commonly used term, and that anyone changing
from C++ would have a lot more to cope with than the terminology of references, so they
might as well jump in with both feet. However, there are people who disagree even with
the term “reference.” I read in one book where it was “completely wrong to say that Java
supports pass by reference,” because Java object identifiers (according to that author) are
actually “object references.” And (he goes on) everything is actually pass by value. So
you’re not passing by reference, you’re “passing an object reference by value.” One could
argue for the precision of such convoluted explanations, but I think my approach
simplifies the understanding of the concept without hurting anything (well, the language
lawyers may claim that I’m lying to you, but I’ll say that I’m providing an appropriate
abstraction.)
Of course, String is not the only type that exists. Java comes with a
plethora of ready-made types. What’s more important is that you can
create your own types. In fact, that’s the fundamental activity in Java
programming, and it’s what you’ll be learning about in the rest of this
book.
Java determines the size of each primitive type. These sizes don’t change
from one machine architecture to another as they do in most languages.
This size invariance is one reason Java programs are so portable.
All numeric types are signed, so don’t go looking for unsigned types.
The primitive data types also have “wrapper” classes for them. That
means that if you want to make a nonprimitive object on the heap to
represent that primitive type, you use the associated wrapper. For
example:
char c = 'x';
Character C = new Character(c);
Or you could also use:
Character C = new Character('x');
The reasons for doing this will be shown in a later chapter.
High-precision numbers
Java includes two classes for performing high-precision arithmetic:
BigInteger and BigDecimal. Although these approximately fit into the
same category as the “wrapper” classes, neither one has a primitive
analogue.
Both classes have methods that provide analogues for the operations that
you perform on primitive types. That is, you can do anything with a
BigInteger or BigDecimal that you can with an int or float, it’s just
that you must use method calls instead of operators. Also, since there’s
more involved, the operations will be slower. You’re exchanging speed for
accuracy.
Consult your online documentation for details about the constructors and
methods you can call for these two classes.
One of the primary goals of Java is safety, so many of the problems that
plague programmers in C and C++ are not repeated in Java. A Java array
is guaranteed to be initialized and cannot be accessed outside of its range.
The range checking comes at the price of having a small amount of
memory overhead on each array as well as verifying the index at run-time,
but the assumption is that the safety and increased productivity is worth
the expense.
When you create an array of objects, you are really creating an array of
references, and each of those references is automatically initialized to a
special value with its own keyword: null. When Java sees null, it
recognizes that the reference in question isn’t pointing to an object. You
must assign an object to each reference before you use it, and if you try to
use a reference that’s still null, the problem will be reported at run-time.
Thus, typical array errors are prevented in Java.
You can also create an array of primitives. Again, the compiler guarantees
initialization because it zeroes the memory for that array.
Note that you cannot do the following, even though it is legal in C and
C++:
{
int x = 12;
{
int x = 96; /* illegal */
}
}
The compiler will announce that the variable x has already been defined.
Thus the C and C++ ability to “hide” a variable in a larger scope is not
allowed because the Java designers thought that it led to confusing
programs.
It turns out that because objects created with new stay around for as long
as you want them, a whole slew of C++ programming problems simply
vanish in Java. The hardest problems seem to occur in C++ because you
don’t get any help from the language in making sure that the objects are
available when they’re needed. And more important, in C++ you must
make sure that you destroy the objects when you’re done with them.
Note carefully that the default values are what Java guarantees when the
variable is used as a member of a class. This ensures that member
variables of primitive types will always be initialized (something C++
doesn’t do), reducing a source of bugs. However, this initial value may not
be correct or even legal for the program you are writing. It’s best to always
explicitly initialize your variables.
This guarantee doesn’t apply to “local” variables—those that are not fields
of a class. Thus, if within a function definition you have:
int x;
Then x will get some arbitrary value (as in C and C++); it will not
automatically be initialized to zero. You are responsible for assigning an
appropriate value before you use x. If you forget, Java definitely improves
on C++: you get a compile-time error telling you the variable might not
have been initialized. (Many C++ compilers will warn you about
uninitialized variables, but in Java these are errors.)
Methods, arguments,
and return values
Up until now, the term function has been used to describe a named
subroutine. The term that is more commonly used in Java is method, as in
“a way to do something.” If you want, you can continue thinking in terms
The fundamental parts of a method are the name, the arguments, the
return type, and the body. Here is the basic form:
returnType methodName( /* argument list */ ) {
/* Method body */
}
The return type is the type of the value that pops out of the method after
you call it. The argument list gives the types and names for the
information you want to pass into the method. The method name and
argument list together uniquely identify the method.
2 static methods, which you’ll learn about soon, can be called for the class, without an
object.
You can also see the use of the return keyword, which does two things.
First, it means “leave the method, I’m done.” Second, if the method
produces a value, that value is placed right after the return statement. In
this case, the return value is produced by evaluating the expression
s.length( ) * 2.
You can return any type you want, but if you don’t want to return
anything at all, you do so by indicating that the method returns void.
Here are some examples:
3 With the usual exception of the aforementioned “special” data types boolean, char,
byte, short, int, long, float, and double. In general, though, you pass objects, which
really means you pass references to objects.
At this point, it can look like a program is just a bunch of objects with
methods that take other objects as arguments and send messages to those
other objects. That is indeed much of what goes on, but in the following
chapter you’ll learn how to do the detailed low-level work by making
decisions within a method. For this chapter, sending messages will
suffice.
Name visibility
A problem in any programming language is the control of names. If you
use a name in one module of the program, and another programmer uses
the same name in another module, how do you distinguish one name
from another and prevent the two names from “clashing?” In C this is a
particular problem because a program is often an unmanageable sea of
names. C++ classes (on which Java classes are based) nest functions
within classes so they cannot clash with function names nested within
other classes. However, C++ still allowed global data and global functions,
so clashing was still possible. To solve this problem, C++ introduced
namespaces using additional keywords.
Java was able to avoid all of this by taking a fresh approach. To produce
an unambiguous name for a library, the specifier used is not unlike an
Internet domain name. In fact, the Java creators want you to use your
In Java 1.0 and Java 1.1 the domain extensions com, edu, org, net, etc.,
were capitalized by convention, so the library would appear:
COM.bruceeckel.utility.foibles. Partway through the development of
Java 2, however, it was discovered that this caused problems, and so now
the entire package name is lowercase.
This mechanism means that all of your files automatically live in their
own namespaces, and each class within a file must have a unique
identifier. So you do not need to learn special language features to solve
this problem—the language takes care of it for you.
What about a class that exists in some other file? You might think that the
compiler should be smart enough to simply go and find it, but there is a
problem. Imagine that you want to use a class of a particular name, but
more than one definition for that class exists (presumably these are
different definitions). Or worse, imagine that you’re writing a program,
and as you’re building it you add a new class to your library that conflicts
with the name of an existing class.
To solve this problem, you must eliminate all potential ambiguities. This
is accomplished by telling the Java compiler exactly what classes you want
using the import keyword. import tells the compiler to bring in a
package, which is a library of classes. (In other languages, a library could
consist of functions and data as well as classes, but remember that all
code in Java must be written inside a class.)
But there are two situations in which this approach is not sufficient. One
is if you want to have only one piece of storage for a particular piece of
data, regardless of how many objects are created, or even if no objects are
created. The other is if you need a method that isn’t associated with any
particular object of this class. That is, you need a method that you can call
even if no objects are created. You can achieve both of these effects with
the static keyword. When you say something is static, it means that data
or method is not tied to any particular object instance of that class. So
even if you’ve never created an object of that class you can call a static
method or access a piece of static data. With ordinary, non-static data
and methods you must create an object and use that object to access the
data or method, since non-static data and methods must know the
particular object they are working with. Of course, since static methods
don’t need any objects to be created before they are used, they cannot
directly access non-static members or methods by simply calling those
other members without referring to a named object (since non-static
members and methods must be tied to a particular object).
To make a data member or method static, you simply place the keyword
before the definition. For example, the following produces a static data
member and initializes it:
class StaticTest {
static int i = 47;
}
Now even if you make two StaticTest objects, there will still be only one
piece of storage for StaticTest.i. Both objects will share the same i.
Consider:
StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest();
At this point, both st1.i and st2.i have the same value of 47 since they
refer to the same piece of memory.
There are two ways to refer to a static variable. As indicated above, you
can name it via an object, by saying, for example, st2.i. You can also refer
to it directly through its class name, something you cannot do with a non-
static member. (This is the preferred way to refer to a static variable
since it emphasizes that variable’s static nature.)
StaticTest.i++;
The ++ operator increments the variable. At this point, both st1.i and
st2.i will have the value 48.
Similar logic applies to static methods. You can refer to a static method
either through an object as you can with any method, or with the special
additional syntax ClassName.method( ). You define a static method in
a similar way:
class StaticFun {
static void incr() { StaticTest.i++; }
}
Like any method, a static method can create or use named objects of its
type, so a static method is often used as a “shepherd” for a flock of
instances of its own type.
4 Some programming environments will flash programs up on the screen and close them
before you've had a chance to see the results. You can put in the following bit of code at the
end of main( ) to pause the output:
try {
System.in.read();
} catch(Exception e) {}
This will pause the output until you press “Enter” (or any other key). This code involves
concepts that will not be introduced until much later in the book, so you won’t understand
it until then, but it will do the trick.
If you go back to the beginning, select java.lang and then System, you’ll
see that the System class has several fields, and if you select out you’ll
discover that it’s a static PrintStream object. Since it’s static you don’t
need to create anything. The out object is always there and you can just
use it. What you can do with this out object is determined by the type it
is: a PrintStream. Conveniently, PrintStream is shown in the
description as a hyperlink, so if you click on that you’ll see a list of all the
methods you can call for PrintStream. There are quite a few and these
will be covered later in this book. For now all we’re interested in is
println( ), which in effect means “print what I’m giving you out to the
console and end with a new line.” Thus, in any Java program you write
The name of the class is the same as the name of the file. When you’re
creating a stand-alone program such as this one, one of the classes in the
file must have the same name as the file. (The compiler complains if you
don’t do this.) That class must contain a method called main( ) with the
signature shown:
public static void main(String[] args) {
The public keyword means that the method is available to the outside
world (described in detail in Chapter 5). The argument to main( ) is an
array of String objects. The args won’t be used in this program, but the
Java compiler insists that they be there because they hold the arguments
invoked on the command line.
Once the JDK is installed, and you’ve set up your computer’s path
information so that it will find javac and java, download and unpack the
On the other hand, if you just get your command prompt back, you can
type:
java HelloDate
and you’ll get the message and the date as output.
This is the process you can use to compile and run each of the programs in
this book. However, you will see that the source code for this book also
has a file called makefile in each chapter, and this contains “make”
commands for automatically building the files for that chapter. See this
book’s Web page at www.BruceEckel.com for details on how to use the
makefiles.
Comment documentation
One of the thoughtful parts of the Java language is that the designers
didn’t consider writing code to be the only important activity—they also
thought about documenting it. Possibly the biggest problem with
documenting code has been maintaining that documentation. If the
documentation and the code are separate, it becomes a hassle to change
the documentation every time you change the code. The solution seems
simple: link the code to the documentation. The easiest way to do this is
to put everything in the same file. To complete the picture, however, you
need a special comment syntax to mark special documentation, and a tool
to extract those comments and put them in a useful form. This is what
Java has done.
The tool to extract the comments is called javadoc. It uses some of the
technology from the Java compiler to look for special comment tags you
put in your programs. It not only extracts the information marked by
these tags, but it also pulls out the class name or method name that
adjoins the comment. This way you can get away with the minimal
amount of work to generate decent program documentation.
The output of javadoc is an HTML file that you can view with your Web
browser. This tool allows you to create and maintain a single source file
and automatically generate useful documentation. Because of javadoc we
Syntax
All of the javadoc commands occur only within /** comments. The
comments end with */ as usual. There are two primary ways to use
javadoc: embed HTML, or use “doc tags.” Doc tags are commands that
start with a ‘@’ and are placed at the beginning of a comment line. (A
leading ‘*’, however, is ignored.)
The output for the above code is an HTML file that has the same standard
format as all the rest of the Java documentation, so users will be
comfortable with the format and can easily navigate your classes. It’s
worth entering the above code, sending it through javadoc and viewing
the resulting HTML file to see the results.
@version
This is of the form:
@version version-information
in which version-information is any significant information you see fit
to include. When the -version flag is placed on the javadoc command
line, the version information will be called out specially in the generated
HTML documentation.
@author
This is of the form:
@author author-information
in which author-information is, presumably, your name, but it could
also include your email address or any other appropriate information.
When the -author flag is placed on the javadoc command line, the author
information will be called out specially in the generated HTML
documentation.
You can have multiple author tags for a list of authors, but they must be
placed consecutively. All the author information will be lumped together
into a single paragraph in the generated HTML.
@param
This is of the form:
@param parameter-name description
in which parameter-name is the identifier in the parameter list, and
description is text that can continue on subsequent lines. The
description is considered finished when a new documentation tag is
encountered. You can have any number of these, presumably one for each
parameter.
@return
This is of the form:
@return description
in which description gives you the meaning of the return value. It can
continue on subsequent lines.
@throws
Exceptions will be demonstrated in Chapter 10, but briefly they are
objects that can be “thrown” out of a method if that method fails.
Although only one exception object can emerge when you call a method, a
particular method might produce any number of different types of
@deprecated
This is used to tag features that were superseded by an improved feature.
The deprecated tag is a suggestion that you no longer use this particular
feature, since sometime in the future it is likely to be removed. A method
that is marked @deprecated causes the compiler to issue a warning if it
is used.
Documentation example
Here is the first Java program again, this time with documentation
comments added:
//: c02:HelloDate.java
import java.util.*;
Coding style
The unofficial standard in Java is to capitalize the first letter of a class
name. If the class name consists of several words, they are run together
(that is, you don’t use underscores to separate the names), and the first
letter of each embedded word is capitalized, such as:
class AllTheColorsOfTheRainbow { // ...
For almost everything else: methods, fields (member variables), and
object reference names, the accepted style is just as it is for classes except
that the first letter of the identifier is lowercase. For example:
class AllTheColorsOfTheRainbow {
int anIntegerRepresentingColors;
void changeTheHueOfTheColor(int newHue) {
// ...
}
// ...
}
Of course, you should remember that the user must also type all these
long names, and so be merciful.
The Java code you will see in the Sun libraries also follows the placement
of open-and-close curly braces that you see used in this book.
5 A tool that I created using Python (see www.Python.org) uses this information to extract
the code files, put them in appropriate subdirectories, and create makefiles.
Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in Java
Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
10. Turn docTest into a file that compiles and then run it through
javadoc. Verify the resulting documentation with your Web
browser.
If you find yourself floundering a bit in this chapter, make sure you go
through the multimedia CD ROM bound into this book: Thinking in C:
Foundations for Java and C++. It contains audio lectures, slides,
exercises, and solutions specifically designed to bring you up to speed
with the C syntax necessary to learn Java.
133
Almost all operators work only with primitives. The exceptions are ‘=’,
‘==’ and ‘!=’, which work with all objects (and are a point of confusion for
objects). In addition, the String class supports ‘+’ and ‘+=’.
Precedence
Operator precedence defines how an expression evaluates when several
operators are present. Java has specific rules that determine the order of
evaluation. The easiest one to remember is that multiplication and
division happen before addition and subtraction. Programmers often
forget the other precedence rules, so you should use parentheses to make
the order of evaluation explicit. For example:
A = X + Y - 2/2 + Z;
has a very different meaning from the same statement with a particular
grouping of parentheses:
A = X + (Y - 2)/(2 + Z);
Assignment
Assignment is performed with the operator =. It means “take the value of
the right-hand side (often called the rvalue) and copy it into the left-hand
side (often called the lvalue). An rvalue is any constant, variable or
expression that can produce a value, but an lvalue must be a distinct,
named variable. (That is, there must be a physical space to store a value.)
For instance, you can assign a constant value to a variable (A = 4;), but
you cannot assign anything to constant value—it cannot be an lvalue. (You
can’t say 4 = A;.)
class Number {
int i;
}
This phenomenon is often called aliasing and it’s a fundamental way that
Java works with objects. But what if you don’t want aliasing to occur in
this case? You could forego the assignment and say:
n1.i = n2.i;
This retains the two separate objects instead of tossing one and tying n1
and n2 to the same object, but you’ll soon realize that manipulating the
fields within objects is messy and goes against good object-oriented
design principles. This is a nontrivial topic, so it is left for Appendix A,
which is devoted to aliasing. In the meantime, you should keep in mind
that assignment for objects can add surprises.
class Letter {
char c;
}
Mathematical operators
The basic mathematical operators are the same as the ones available in
most programming languages: addition (+), subtraction (-), division (/),
multiplication (*) and modulus (%, which produces the remainder from
integer division). Integer division truncates, rather than rounds, the
result.
The modulus operator, when used with the result of the random number
generator, limits the result to an upper bound of the operand minus one
(99 in this case).
Two of the nicer shortcuts are the increment and decrement operators
(often referred to as the auto-increment and auto-decrement operators).
The decrement operator is -- and means “decrease by one unit.” The
There are two versions of each type of operator, often called the prefix and
postfix versions. Pre-increment means the ++ operator appears before
the variable or expression, and post-increment means the ++ operator
appears after the variable or expression. Similarly, pre-decrement means
the -- operator appears before the variable or expression, and post-
decrement means the -- operator appears after the variable or expression.
For pre-increment and pre-decrement, (i.e., ++a or --a), the operation is
performed and the value is produced. For post-increment and post-
decrement (i.e. a++ or a--), the value is produced, then the operation is
performed. As an example:
//: c03:AutoInc.java
// Demonstrates the ++ and -- operators.
The increment operator is one explanation for the name C++, implying
“one step beyond C.” In an early Java speech, Bill Joy (one of the
creators), said that “Java=C++--” (C plus plus minus minus), suggesting
that Java is C++ with the unnecessary hard parts removed and therefore a
much simpler language. As you progress in this book you’ll see that many
parts are simpler, and yet Java isn’t that much easier than C++.
Relational operators
Relational operators generate a boolean result. They evaluate the
relationship between the values of the operands. A relational expression
produces true if the relationship is true, and false if the relationship is
untrue. The relational operators are less than (<), greater than (>), less
than or equal to (<=), greater than or equal to (>=), equivalent (==) and
not equivalent (!=). Equivalence and nonequivalence works with all built-
in data types, but the other comparisons won’t work with type boolean.
class Value {
int i;
}
Logical operators
The logical operators AND (&&), OR (||) and NOT (!) produce a boolean
value of true or false based on the logical relationship of its arguments.
This example uses the relational and logical operators:
//: c03:Bool.java
// Relational and logical operators.
import java.util.*;
You can replace the definition for int in the above program with any other
primitive data type except boolean. Be aware, however, that the
comparison of floating-point numbers is very strict. A number that is the
tiniest fraction different from another number is still “not equal.” A
number that is the tiniest bit above zero is still nonzero.
Short-circuiting
When dealing with logical operators you run into a phenomenon called
“short circuiting.” This means that the expression will be evaluated only
Bitwise operators
The bitwise operators allow you to manipulate individual bits in an
integral primitive data type. Bitwise operators perform boolean algebra on
the corresponding bits in the two arguments to produce the result.
The bitwise operators come from C’s low-level orientation; you were often
manipulating hardware directly and had to set the bits in hardware
registers. Java was originally designed to be embedded in TV set-top
boxes, so this low-level orientation still made sense. However, you
probably won’t use the bitwise operators much.
The bitwise AND operator (&) produces a one in the output bit if both
input bits are one; otherwise it produces a zero. The bitwise OR operator
(|) produces a one in the output bit if either input bit is a one and
produces a zero only if both input bits are zero. The bitwise EXCLUSIVE
OR, or XOR (^), produces a one in the output bit if one or the other input
bit is a one, but not both. The bitwise NOT (~, also called the ones
complement operator) is a unary operator; it takes only one argument.
(All other bitwise operators are binary operators.) Bitwise NOT produces
the opposite of the input bit—a one if the input bit is zero, a zero if the
input bit is one.
The bitwise operators and logical operators use the same characters, so it
is helpful to have a mnemonic device to help you remember the meanings:
since bits are “small,” there is only one character in the bitwise operators.
Shift operators
The shift operators also manipulate bits. They can be used solely with
primitive, integral types. The left-shift operator (<<) produces the
operand to the left of the operator shifted to the left by the number of bits
specified after the operator (inserting zeroes at the lower-order bits). The
signed right-shift operator (>>) produces the operand to the left of the
operator shifted to the right by the number of bits specified after the
operator. The signed right shift >> uses sign extension: if the value is
positive, zeroes are inserted at the higher-order bits; if the value is
negative, ones are inserted at the higher-order bits. Java has also added
the unsigned right shift >>>, which uses zero extension: regardless of the
sign, zeroes are inserted at the higher-order bits. This operator does not
exist in C or C++.
If you shift a char, byte, or short, it will be promoted to int before the
shift takes place, and the result will be an int. Only the five low-order bits
of the right-hand side will be used. This prevents you from shifting more
than the number of bits in an int. If you’re operating on a long, you’ll get
a long result. Only the six low-order bits of the right-hand side will be
used so you can’t shift more than the number of bits in a long.
Shifts can be combined with the equal sign (<<= or >>= or >>>=). The
lvalue is replaced by the lvalue shifted by the rvalue. There is a problem,
however, with the unsigned right shift combined with assignment. If you
use it with byte or short you don’t get the correct results. Instead, these
are promoted to int and right shifted, but then truncated as they are
Here’s an example that demonstrates the use of all the operators involving
bits:
//: c03:BitManipulation.java
// Using the bitwise operators.
import java.util.*;
long l = rand.nextLong();
long m = rand.nextLong();
pBinLong("-1L", -1L);
pBinLong("+1L", +1L);
long ll = 9223372036854775807L;
pBinLong("maxpos", ll);
long lln = -9223372036854775808L;
pBinLong("maxneg", lln);
pBinLong("l", l);
pBinLong("~l", ~l);
pBinLong("-l", -l);
pBinLong("m", m);
pBinLong("l & m", l & m);
pBinLong("l | m", l | m);
pBinLong("l ^ m", l ^ m);
pBinLong("l << 5", l << 5);
pBinLong("l >> 5", l >> 5);
pBinLong("(~l) >> 5", (~l) >> 5);
pBinLong("l >>> 5", l >>> 5);
pBinLong("(~l) >>> 5", (~l) >>> 5);
}
static void pBinInt(String s, int i) {
System.out.println(
s + ", int: " + i + ", binary: ");
As well as demonstrating the effect of all the bitwise operators for int and
long, this example also shows the minimum, maximum, +1 and -1 values
for int and long so you can see what they look like. Note that the high bit
represents the sign: 0 means positive and 1 means negative. The output
for the int portion looks like this:
-1, int: -1, binary:
11111111111111111111111111111111
+1, int: 1, binary:
00000000000000000000000000000001
maxpos, int: 2147483647, binary:
01111111111111111111111111111111
The conditional operator can be used for its side effects or for the value it
produces, but in general you want the value since that’s what makes the
operator distinct from the if-else. Here’s an example:
static int ternary(int i) {
return i < 10 ? i * 100 : i * 10;
}
You can see that this code is more compact than what you’d need to write
without the ternary operator:
static int alternative(int i) {
if (i < 10)
return i * 100;
else
return i * 10;
}
The second form is easier to understand, and doesn’t require a lot more
typing. So be sure to ponder your reasons when choosing the ternary
operator.
Casting operators
The word cast is used in the sense of “casting into a mold.” Java will
automatically change one type of data into another when appropriate. For
instance, if you assign an integral value to a floating-point variable, the
compiler will automatically convert the int to a float. Casting allows you
to make this type conversion explicit, or to force it when it wouldn’t
normally happen.
To perform a cast, put the desired data type (including all modifiers)
inside parentheses to the left of any value. Here’s an example:
void casts() {
int i = 200;
long l = (long)i;
long l2 = (long)200;
}
As you can see, it’s possible to perform a cast on a numeric value as well
as on a variable. In both casts shown here, however, the cast is
superfluous, since the compiler will automatically promote an int value to
In C and C++, casting can cause some headaches. In Java, casting is safe,
with the exception that when you perform a so-called narrowing
conversion (that is, when you go from a data type that can hold more
information to one that doesn’t hold as much) you run the risk of losing
information. Here the compiler forces you to do a cast, in effect saying
“this can be a dangerous thing to do—if you want me to do it anyway you
must make the cast explicit.” With a widening conversion an explicit cast
is not needed because the new type will more than hold the information
from the old type so that no information is ever lost.
Java allows you to cast any primitive type to any other primitive type,
except for boolean, which doesn’t allow any casting at all. Class types do
not allow casting. To convert one to the other there must be special
methods. (String is a special case, and you’ll find out later in this book
that objects can be cast within a family of types; an Oak can be cast to a
Tree and vice-versa, but not to a foreign type such as a Rock.)
Literals
Ordinarily when you insert a literal value into a program the compiler
knows exactly what type to make it. Sometimes, however, the type is
ambiguous. When this happens you must guide the compiler by adding
some extra information in the form of characters associated with the
literal value. The following code shows these characters:
//: c03:Literals.java
class Literals {
char c = 0xffff; // max char hex value
byte b = 0x7f; // max byte hex value
short s = 0x7fff; // max short hex value
int i1 = 0x2f; // Hexadecimal (lowercase)
int i2 = 0X2F; // Hexadecimal (uppercase)
int i3 = 0177; // Octal (leading zero)
// Hex and Oct also work with long.
long n1 = 200L; // long suffix
long n2 = 200l; // long suffix
Octal (base 8) is denoted by a leading zero in the number and digits from
0-7. There is no literal representation for binary numbers in C, C++ or
Java.
Exponents use a notation that I’ve always found rather dismaying: 1.39 e-
47f. In science and engineering, ‘e’ refers to the base of natural
logarithms, approximately 2.718. (A more precise double value is
available in Java as Math.E.) This is used in exponentiation expressions
such as 1.39 x e-47, which means 1.39 x 2.718-47. However, when FORTRAN
was invented they decided that e would naturally mean “ten to the
power,” which is an odd decision because FORTRAN was designed for
science and engineering and one would think its designers would be
Note that you don’t need to use the trailing character when the compiler
can figure out the appropriate type. With
long n3 = 200;
there’s no ambiguity, so an L after the 200 would be superfluous.
However, with
float f4 = 1e-47f; // 10 to the power
the compiler normally takes exponential numbers as doubles, so without
the trailing f it will give you an error telling you that you must use a cast
to convert double to float.
Promotion
You’ll discover that if you perform any mathematical or bitwise operations
on primitive data types that are smaller than an int (that is, char, byte,
or short), those values will be promoted to int before performing the
operations, and the resulting value will be of type int. So if you want to
assign back into the smaller type, you must use a cast. (And, since you’re
assigning back into a smaller type, you might be losing information.) In
general, the largest data type in an expression is the one that determines
1 John Kirkham writes, “I started computing in 1962 using FORTRAN II on an IBM 1620.
At that time, and throughout the 1960s and into the 1970s, FORTRAN was an all
uppercase language. This probably started because many of the early input devices were
old teletype units that used 5 bit Baudot code, which had no lowercase capability. The ‘E’
in the exponential notation was also always upper case and was never confused with the
natural logarithm base ‘e’, which is always lowercase. The ‘E’ simply stood for exponential,
which was for the base of the number system used—usually 10. At the time octal was also
widely used by programmers. Although I never saw it used, if I had seen an octal number
in exponential notation I would have considered it to be base 8. The first time I remember
seeing an exponential using a lowercase ‘e’ was in the late 1970s and I also found it
confusing. The problem arose as lowercase crept into FORTRAN, not at its beginning. We
actually had functions to use if you really wanted to use the natural logarithm base, but
they were all uppercase.”
Java does not need a sizeof( ) operator for this purpose because all the
data types are the same size on all machines. You do not need to think
about portability on this level—it is designed into the language.
Precedence revisited
Upon hearing me complain about the complexity of remembering
operator precedence during one of my seminars, a student suggested a
mnemonic that is simultaneously a commentary: “Ulcer Addicts Really
Like C A lot.”
Of course, with the shift and bitwise operators distributed around the
table it is not a perfect mnemonic, but for non-bit operations it works.
class AllOps {
// To accept the results of a boolean test:
void f(boolean b) {}
void boolTest(boolean x, boolean y) {
// Arithmetic operators:
//! x = x * y;
//! x = x / y;
//! x = x % y;
//! x = x + y;
//! x = x - y;
//! x++;
//! x--;
//! x = +y;
//! x = -y;
// Relational and logical:
//! f(x > y);
//! f(x >= y);
//! f(x < y);
//! f(x <= y);
f(x == y);
f(x != y);
f(!y);
x = x && y;
x = x || y;
// Bitwise operators:
//! x = ~y;
x = x & y;
x = x | y;
In char, byte, and short you can see the effect of promotion with the
arithmetic operators. Each arithmetic operation on any of those types
results in an int result, which must be explicitly cast back to the original
type (a narrowing conversion that might lose information) to assign back
to that type. With int values, however, you do not need to cast, because
everything is already an int. Don’t be lulled into thinking everything is
safe, though. If you multiply two ints that are big enough, you’ll overflow
the result. The following example demonstrates this:
//: c03:Overflow.java
// Surprise! Java lets you overflow.
You can see that, with the exception of boolean, any primitive type can
be cast to any other primitive type. Again, you must be aware of the effect
of a narrowing conversion when casting to a smaller type, otherwise you
might unknowingly lose information during the cast.
Execution control
Java uses all of C’s execution control statements, so if you’ve programmed
with C or C++ then most of what you see will be familiar. Most procedural
programming languages have some kind of control statements, and there
is often overlap among languages. In Java, the keywords include if-else,
while, do-while, for, and a selection statement called switch. Java
does not, however, support the much-maligned goto (which can still be
the most expedient way to solve certain types of problems). You can still
do a goto-like jump, but it is much more constrained than a typical goto.
return
The return keyword has two purposes: it specifies what value a method
will return (if it doesn’t have a void return value) and it causes that value
to be returned immediately. The test( ) method above can be rewritten to
take advantage of this:
//: c03:IfElse2.java
public class IfElse2 {
static int test(int testval, int target) {
int result = 0;
if(testval > target)
return +1;
else if(testval < target)
return -1;
else
return 0; // Match
}
public static void main(String[] args) {
System.out.println(test(10, 5));
System.out.println(test(5, 10));
System.out.println(test(5, 5));
}
} ///:~
There’s no need for else because the method will not continue after
executing a return.
Iteration
while, do-while and for control looping and are sometimes classified as
iteration statements. A statement repeats until the controlling Boolean-
expression evaluates to false. The form for a while loop is
while(Boolean-expression)
statement
The Boolean-expression is evaluated once at the beginning of the loop
and again before each further iteration of the statement.
do-while
The form for do-while is
do
statement
while(Boolean-expression);
The sole difference between while and do-while is that the statement of
the do-while always executes at least once, even if the expression
evaluates to false the first time. In a while, if the conditional is false the
first time the statement never executes. In practice, do-while is less
common than while.
for
A for loop performs initialization before the first iteration. Then it
performs conditional testing and, at the end of each iteration, some form
of “stepping.” The form of the for loop is:
You can define multiple variables within a for statement, but they must
be of the same type:
for(int i = 0, j = 1;
i < 10 && j != 11;
i++, j++)
This program shows examples of break and continue within for and
while loops:
//: c03:BreakAndContinue.java
// Demonstrates break and continue keywords.
A second form of the infinite loop is for(;;). The compiler treats both
while(true) and for(;;) in the same way so whichever one you use is a
matter of programming taste.
As is typical in situations like this, the middle ground is the most fruitful.
The problem is not the use of goto, but the overuse of goto—in rare
situations goto is actually the best way to structure control flow.
Note that break breaks out of the for loop, and that the increment-
expression doesn’t occur until the end of the pass through the for loop.
Since break skips the increment expression, the increment is performed
directly in the case of i == 3. The continue outer statement in the case
of i == 7 also goes to the top of the loop and also skips the increment, so
it too is incremented directly.
2. A labeled continue goes to the label and re-enters the loop right
after that label.
4. A labeled break drops out of the bottom of the end of the loop
denoted by the label.
switch
The switch is sometimes classified as a selection statement. The switch
statement selects from among pieces of code based on the value of an
integral expression. Its form is:
switch(integral-selector) {
case integral-value1 : statement; break;
case integral-value2 : statement; break;
case integral-value3 : statement; break;
case integral-value4 : statement; break;
case integral-value5 : statement; break;
// ...
default: statement;
}
Integral-selector is an expression that produces an integral value. The
switch compares the result of integral-selector to each integral-value. If
it finds a match, the corresponding statement (simple or compound)
executes. If no match occurs, the default statement executes.
You will notice in the above definition that each case ends with a break,
which causes execution to jump to the end of the switch body. This is the
conventional way to build a switch statement, but the break is optional.
If it is missing, the code for the following case statements execute until a
break is encountered. Although you don’t usually want this kind of
behavior, it can be useful to an experienced programmer. Note the last
statement, following the default, doesn’t have a break because the
execution just falls through to where the break would have taken it
anyway. You could put a break at the end of the default statement with
no harm if you considered it important for style’s sake.
Calculation details
The statement:
char c = (char)(Math.random() * 26 + 'a');
deserves a closer look. Math.random( ) produces a double, so the
value 26 is converted to a double to perform the multiplication, which
also produces a double. This means that ‘a’ must be converted to a
double to perform the addition. The double result is turned back into a
char with a cast.
What does the cast to char do? That is, if you have the value 29.7 and you
cast it to a char, is the resulting value 30 or 29? The answer to this can be
seen in this example:
//: c03:CastingNumbers.java
// What happens when you cast a float
// or double to an integral value?
Summary
This chapter concludes the study of fundamental features that appear in
most programming languages: calculation, operator precedence, type
2 Chuck Allison writes: The total number of numbers in a floating-point number system is
2(M-m+1)b^(p-1) + 1
where b is the base (usually 2), p is the precision (digits in the mantissa), M is the largest
exponent, and m is the smallest exponent. IEEE 754 uses:
M = 1023, m = -1022, p = 53, b = 2
so the total number of numbers is
2(1023+1022+1)2^52
= 2((2^10-1) + (2^10-1))2^52
= (2^10-1)2^54
= 2^64 - 2^54
Half of these numbers (corresponding to exponents in the range [-1022, 0]) are less than 1
in magnitude (both positive and negative), so 1/4 of that expression, or 2^62 - 2^52 + 1
(approximately 2^62) is in the range [0,1). See my paper at
http://www.freshsources.com/1995006a.htm (last of text).
Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in Java
Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
3. From the sections labeled “if-else” and “return”, put the methods
test( ) and test2( ) into a working program.
6. Write a function that takes two String arguments, and uses all the
Boolean comparisons to compare the two Strings and print the
results. For the == and !=, also perform the equals( ) test. In
main( ), call your function with some different String objects.
9. Write a program that uses two nested for loops and the modulus
operator (%) to detect and print prime numbers (integral numbers
10. Create a switch statement that prints a message for each case, and
put the switch inside a for loop that tries each case. Put a break
after each case and test it, then remove the breaks and see what
happens.
Guaranteed initialization
with the constructor
You can imagine creating a method called initialize( ) for every class you
write. The name is a hint that it should be called before using the object.
Unfortunately, this means the user must remember to call the method. In
Java, the class designer can guarantee initialization of every object by
providing a special method called a constructor. If a class has a
constructor, Java automatically calls that constructor when an object is
191
created, before users can even get their hands on it. So initialization is
guaranteed.
The next challenge is what to name this method. There are two issues. The
first is that any name you use could clash with a name you might like to
use as a member in the class. The second is that because the compiler is
responsible for calling the constructor, it must always know which
method to call. The C++ solution seems the easiest and most logical, so
it’s also used in Java: the name of the constructor is the same as the name
of the class. It makes sense that such a method will be called
automatically on initialization.
class Rock {
Rock() { // This is the constructor
System.out.println("Creating Rock");
}
}
Note that the coding style of making the first letter of all methods
lowercase does not apply to constructors, since the name of the
constructor must match the name of the class exactly.
class Rock2 {
Rock2(int i) {
System.out.println(
"Creating Rock number " + i);
}
}
Constructors eliminate a large class of problems and make the code easier
to read. In the preceding code fragment, for example, you don’t see an
explicit call to some initialize( ) method that is conceptually separate
from definition. In Java, definition and initialization are unified
concepts—you can’t have one without the other.
Method overloading
One of the important features in any programming language is the use of
names. When you create an object, you give a name to a region of storage.
A method is a name for an action. By using names to describe your
system, you create a program that is easier for people to understand and
change. It’s a lot like writing prose—the goal is to communicate with your
readers.
You refer to all objects and methods by using names. Well-chosen names
make it easier for you and others to understand your code.
class Tree {
int height;
Tree() {
prt("Planting a seedling");
height = 0;
}
Tree(int i) {
prt("Creating new Tree that is "
+ i + " feet tall");
height = i;
}
void info() {
prt("Tree is " + height
+ " feet tall");
}
void info(String s) {
prt(s + ": Tree is "
+ height + " feet tall");
}
static void prt(String s) {
System.out.println(s);
}
}
You might also want to call the info( ) method in more than one way. For
example, with a String argument if you have an extra message you want
printed, and without if you have nothing more to say. It would seem
strange to give two separate names to what is obviously the same concept.
Fortunately, method overloading allows you to use the same name for
both.
If you think about this for a second, it makes sense: how else could a
programmer tell the difference between two methods that have the same
name, other than by the types of their arguments?
1 In some of the Java literature from Sun they instead refer to these with the clumsy but
descriptive name “no-arg constructors.” The term “default constructor” has been in use for
many years and so I will use that.
void testConstVal() {
prt("Testing with 5");
f1(5);f2(5);f3(5);f4(5);f5(5);f6(5);f7(5);
}
void testChar() {
char x = 'x';
prt("char argument:");
void testDouble() {
double x = 0;
prt("double argument:");
f1(x);f2((float)x);f3((long)x);f4((int)x);
f5((short)x);f6((byte)x);f7((char)x);
}
public static void main(String[] args) {
Demotion p = new Demotion();
p.testDouble();
}
} ///:~
Here, the methods take narrower primitive values. If your argument is
wider then you must cast to the necessary type using the type name in
parentheses. If you don’t do this, the compiler will issue an error message.
Default constructors
As mentioned previously, a default constructor (a.k.a. a “no-arg”
constructor) is one without arguments, used to create a “vanilla object.” If
you create a class that has no constructors, the compiler will automatically
create a default constructor for you. For example:
//: c04:DefaultConstructor.java
class Bird {
int i;
}
If there’s only one method called f( ), how can that method know whether
it’s being called for the object a or b?
Suppose you’re inside a method and you’d like to get the reference to the
current object. Since that reference is passed secretly by the compiler,
there’s no identifier for it. However, for this purpose there’s a keyword:
this. The this keyword—which can be used only inside a method—
produces the reference to the object the method has been called for. You
can treat this reference just like any other object reference. Keep in mind
that if you’re calling a method of your class from within another method
of your class, you don’t need to use this; you simply call the method. The
current this reference is automatically used for the other method. Thus
you can say:
class Apricot {
void pick() { /* ... */ }
void pit() { pick(); /* ... */ }
}
Inside pit( ), you could say this.pick( ) but there’s no need to. The
compiler does it for you automatically. The this keyword is used only for
those special cases in which you need to explicitly use the reference to the
current object. For example, it’s often used in return statements when
you want to return the reference to the current object:
//: c04:Leaf.java
// Simple use of the "this" keyword.
Normally, when you say this, it is in the sense of “this object” or “the
current object,” and by itself it produces the reference to the current
object. In a constructor, the this keyword takes on a different meaning
when you give it an argument list: it makes an explicit call to the
constructor that matches that argument list. Thus you have a
straightforward way to call other constructors:
//: c04:Flower.java
// Calling constructors with "this."
This example also shows another way you’ll see this used. Since the name
of the argument s and the name of the member data s are the same,
there’s an ambiguity. You can resolve it by saying this.s to refer to the
member data. You’ll often see this form used in Java code, and it’s used in
numerous places in this book.
In print( ) you can see that the compiler won’t let you call a constructor
from inside any method other than a constructor.
Some people argue that static methods are not object-oriented since they
do have the semantics of a global function; with a static method you
don’t send a message to an object, since there’s no this. This is probably a
fair argument, and if you find yourself using a lot of static methods you
should probably rethink your strategy. However, statics are pragmatic
and there are times when you genuinely need them, so whether or not
they are “proper OOP” should be left to the theoreticians. Indeed, even
Smalltalk has the equivalent in its “class methods.”
2 The one case in which this is possible occurs if you pass a reference to an object into the
static method. Then, via the reference (which is now effectively this), you can call non-
static methods and access non-static fields. But typically if you want to do something like
this you’ll just make an ordinary, non-static method.
Does this mean that if your object contains other objects finalize( )
should explicitly release those objects? Well, no—the garbage collector
takes care of the release of all object memory regardless of how the object
is created. It turns out that the need for finalize( ) is limited to special
cases, in which your object can allocate some storage in some way other
than creating an object. But, you might observe, everything in Java is an
object so how can this be?
After reading this, you probably get the idea that you won’t use
finalize( ) much. You’re correct; it is not the appropriate place for
normal cleanup to occur. So where should normal cleanup be performed?
One of the things finalize( ) can be useful for is observing the process of
garbage collection. The following example shows you what’s going on and
summarizes the previous descriptions of garbage collection:
//: c04:Garbage.java
// Demonstration of the garbage
// collector and finalization
class Chair {
static boolean gcrun = false;
static boolean f = false;
static int created = 0;
static int finalized = 0;
int i;
Chair() {
i = ++created;
if(created == 47)
System.out.println("Created 47");
}
Two other static variables, created and finalized, keep track of the
number of Chairs created versus the number that get finalized by the
garbage collector. Finally, each Chair has its own (non-static) int i so it
can keep track of what number it is. When Chair number 47 is finalized,
the flag is set to true to bring the process of Chair creation to a stop.
The behavior of this program and the version in the first edition of this
book shows that the whole issue of garbage collection and finalization has
been evolving, with much of the evolution happening behind closed doors.
In fact, by the time you read this, the behavior of the program may have
changed once again.
However, you will see that only if System.gc( ) is called after all the
objects are created and discarded will all the finalizers be called. If you do
not call System.gc( ), then only some of the objects will be finalized. In
Java 1.1, a method System.runFinalizersOnExit( ) was introduced
that caused programs to run all the finalizers as they exited, but the
design turned out to be buggy and the method was deprecated. This is yet
another clue that the Java designers were thrashing about trying to solve
the garbage collection and finalization problem. We can only hope that
things have been worked out in Java 2.
The preceding program shows that the promise that finalizers will always
be run holds true, but only if you explicitly force it to happen yourself. If
you don’t cause System.gc( ) to be called, you’ll get an output like this:
Created 47
Beginning to finalize after 3486 Chairs have been
created
Finalizing Chair #47, Setting flag to stop Chair
creation
After all Chairs have been created:
total created = 3881, total finalized = 2684
class Book {
3 A term coined by Bill Venners (www.artima.com) during a seminar that he and I were
giving together.
For example, you can think of the C++ heap as a yard where each object
stakes out its own piece of turf. This real estate can become abandoned
sometime later and must be reused. In some JVMs, the Java heap is quite
different; it’s more like a conveyor belt that moves forward every time you
allocate a new object. This means that object storage allocation is
remarkably rapid. The “heap pointer” is simply moved forward into virgin
territory, so it’s effectively the same as C++’s stack allocation. (Of course,
there’s a little extra overhead for bookkeeping but it’s nothing like
searching for storage.)
Now you might observe that the heap isn’t in fact a conveyor belt, and if
you treat it that way you’ll eventually start paging memory a lot (which is
a big performance hit) and later run out. The trick is that the garbage
collector steps in and while it collects the garbage it compacts all the
objects in the heap so that you’ve effectively moved the “heap pointer”
closer to the beginning of the conveyor belt and further away from a page
fault. The garbage collector rearranges things and makes it possible for
the high-speed, infinite-free-heap model to be used while allocating
storage.
To understand how this works, you need to get a little better idea of the
way the different garbage collector (GC) schemes work. A simple but slow
GC technique is reference counting. This means that each object contains
a reference counter, and every time a reference is attached to an object the
reference count is increased. Every time a reference goes out of scope or is
set to null, the reference count is decreased. Thus, managing reference
counts is a small but constant overhead that happens throughout the
lifetime of your program. The garbage collector moves through the entire
list of objects and when it finds one with a reference count of zero it
releases that storage. The one drawback is that if objects circularly refer to
each other they can have nonzero reference counts while still being
garbage. Locating such self-referential groups requires significant extra
There are two issues that make these so-called “copy collectors”
inefficient. The first is the idea that you have two heaps and you slosh all
The second issue is the copying. Once your program becomes stable it
might be generating little or no garbage. Despite that, a copy collector will
still copy all the memory from one place to another, which is wasteful. To
prevent this, some JVMs detect that no new garbage is being generated
and switch to a different scheme (this is the “adaptive” part). This other
scheme is called mark and sweep, and it’s what earlier versions of Sun’s
JVM used all the time. For general use, mark and sweep is fairly slow, but
when you know you’re generating little or no garbage it’s fast.
Mark and sweep follows the same logic of starting from the stack and
static storage and tracing through all the references to find live objects.
However, each time it finds a live object that object is marked by setting a
flag in it, but the object isn’t collected yet. Only when the marking process
is finished does the sweep occur. During the sweep, the dead objects are
released. However, no copying happens, so if the collector chooses to
compact a fragmented heap it does so by shuffling objects around.
The “stop-and-copy” refers to the idea that this type of garbage collection
is not done in the background; instead, the program is stopped while the
GC occurs. In the Sun literature you’ll find many references to garbage
collection as a low-priority background process, but it turns out that the
GC was not implemented that way, at least in earlier versions of the Sun
JVM. Instead, the Sun garbage collector ran when memory got low. In
addition, mark-and-sweep requires that the program be stopped.
Member initialization
Java goes out of its way to guarantee that variables are properly initialized
before they are used. In the case of variables that are defined locally to a
method, this guarantee comes in the form of a compile-time error. So if
you say:
void f() {
int i;
i++;
}
you’ll get an error message that says that i might not have been initialized.
Of course, the compiler could have given i a default value, but it’s more
likely that this is a programmer error and a default value would have
class Measurement {
boolean t;
char c;
byte b;
short s;
int i;
long l;
float f;
double d;
void print() {
System.out.println(
"Data type Initial value\n" +
"boolean " + t + "\n" +
"char [" + c + "] "+ (int)c +"\n"+
"byte " + b + "\n" +
"short " + s + "\n" +
"int " + i + "\n" +
"long " + l + "\n" +
"float " + f + "\n" +
"double " + d);
}
}
You’ll see later that when you define an object reference inside a class
without initializing it to a new object, that reference is given a special
value of null (which is a Java keyword).
You can see that even though the values are not specified, they
automatically get initialized. So at least there’s no threat of working with
uninitialized variables.
Specifying initialization
What happens if you want to give a variable an initial value? One direct
way to do this is simply to assign the value at the point you define the
variable in the class. (Notice you cannot do this in C++, although C++
novices always try.) Here the field definitions in class Measurement are
changed to provide initial values:
class Measurement {
boolean b = true;
char c = 'x';
byte B = 47;
short s = 0xff;
int i = 999;
long l = 1;
float f = 3.14f;
Constructor initialization
The constructor can be used to perform initialization, and this gives you
greater flexibility in your programming since you can call methods and
perform actions at run-time to determine the initial values. There’s one
thing to keep in mind, however: you aren’t precluding the automatic
initialization, which happens before the constructor is entered. So, for
example, if you say:
class Counter {
int i;
Counter() { i = 7; }
// . . .
then i will first be initialized to 0, then to 7. This is true with all the
primitive types and with object references, including those that are given
explicit initialization at the point of definition. For this reason, the
compiler doesn’t try to force you to initialize elements in the constructor
at any particular place, or before they are used—initialization is already
guaranteed4.
Order of initialization
Within a class, the order of initialization is determined by the order that
the variables are defined within the class. The variable definitions may be
scattered throughout and in between method definitions, but the
variables are initialized before any methods can be called—even the
constructor. For example:
//: c04:OrderOfInitialization.java
// Demonstrates initialization order.
4 In contrast, C++ has the constructor initializer list that causes initialization to occur
before entering the constructor body, and is enforced for objects. See Thinking in C++, 2nd
edition (available on this book’s CD ROM and at www.BruceEckel.com).
class Card {
Tag t1 = new Tag(1); // Before constructor
Card() {
// Indicate we're in the constructor:
System.out.println("Card()");
t3 = new Tag(33); // Reinitialize t3
}
Tag t2 = new Tag(2); // After constructor
void f() {
System.out.println("f()");
}
Tag t3 = new Tag(3); // At end
}
class Bowl {
Bowl(int marker) {
System.out.println("Bowl(" + marker + ")");
}
void f(int marker) {
System.out.println("f(" + marker + ")");
}
}
class Table {
static Bowl b1 = new Bowl(1);
Table() {
System.out.println("Table()");
b2.f(1);
}
void f2(int marker) {
System.out.println("f2(" + marker + ")");
class Cupboard {
Bowl b3 = new Bowl(3);
static Bowl b4 = new Bowl(4);
Cupboard() {
System.out.println("Cupboard()");
b4.f(2);
}
void f3(int marker) {
System.out.println("f3(" + marker + ")");
}
static Bowl b5 = new Bowl(5);
}
1. The first time an object of type Dog is created, or the first time a
static method or static field of class Dog is accessed, the Java
interpreter must locate Dog.class, which it does by searching
through the classpath.
class Cup {
Cup(int marker) {
System.out.println("Cup(" + marker + ")");
class Cups {
static Cup c1;
static Cup c2;
static {
c1 = new Cup(1);
c2 = new Cup(2);
}
Cups() {
System.out.println("Cups()");
}
}
The compiler doesn’t allow you to tell it how big the array is. This brings
us back to that issue of “references.” All that you have at this point is a
reference to an array, and there’s been no space allocated for the array. To
create storage for the array you must write an initialization expression.
For arrays, initialization can appear anywhere in your code, but you can
also use a special kind of initialization expression that must occur at the
point where the array is created. This special initialization is a set of
values surrounded by curly braces. The storage allocation (the equivalent
of using new) is taken care of by the compiler in this case. For example:
int[] a1 = { 1, 2, 3, 4, 5 };
So why would you ever define an array reference without an array?
5 See Thinking in C++, 2nd edition for a complete description of C++ aggregate
initialization.
Of course, the array could also have been defined and initialized in the
same statement:
int[] a = new int[pRand(20)];
If you’re dealing with an array of nonprimitive objects, you must always
use new. Here, the reference issue comes up again because what you
create is an array of references. Consider the wrapper type Integer,
which is a class and not a primitive:
//: c04:ArrayClassObj.java
Take a look at the formation of the String object inside the print
statements. You can see that the reference to the Integer object is
automatically converted to produce a String representing the value
inside the object.
class A { int i; }
Multidimensional arrays
Java allows you to easily create multidimensional arrays:
//: c04:MultiDimArray.java
// Creating multidimensional arrays.
import java.util.*;
You will see from the output that array values are automatically initialized
to zero if you don’t give them an explicit initialization value.
Summary
This seemingly elaborate mechanism for initialization, the constructor,
should give you a strong hint about the critical importance placed on
initialization in the language. As Stroustrup was designing C++, one of the
first observations he made about productivity in C was that improper
initialization of variables causes a significant portion of programming
problems. These kinds of bugs are hard to find, and similar issues apply to
improper cleanup. Because constructors allow you to guarantee proper
initialization and cleanup (the compiler will not allow an object to be
created without the proper constructor calls), you get complete control
and safety.
Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in Java
Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
9. Create a class with two methods. Within the first method, call the
second method twice: the first time without using this, and the
second time using this.
10. Create a class with two (overloaded) constructors. Using this, call
the second constructor inside the first one.
13. Create a class called Tank that can be filled and emptied, and has
a death condition that it must be empty when the object is cleaned
up. Write a finalize( ) that verifies this death condition. In
main( ), test the possible scenarios that can occur when your
Tank is used.
14. Create a class containing an int and a char that are not initialized,
and print their values to verify that Java performs default
initialization.
16. Create a class with a String field that is initialized at the point of
definition, and another one that is initialized by the constructor.
What is the difference between the two approaches?
17. Create a class with a static String field that is initialized at the
point of definition, and another one that is initialized by the static
To solve this problem, Java provides access specifiers to allow the library
creator to say what is available to the client programmer and what is not.
The levels of access control from “most access” to “least access” are
public, protected, “friendly” (which has no keyword), and private.
From the previous paragraph you might think that, as a library designer,
you’ll want to keep everything as “private” as possible, and expose only
the methods that you want the client programmer to use. This is exactly
right, even though it’s often counterintuitive for people who program in
243
other languages (especially C) and are used to accessing everything
without restriction. By the end of this chapter you should be convinced of
the value of access control in Java.
The concept of a library of components and the control over who can
access the components of that library is not complete, however. There’s
still the question of how the components are bundled together into a
cohesive library unit. This is controlled with the package keyword in
Java, and the access specifiers are affected by whether a class is in the
same package or in a separate package. So to begin this chapter, you’ll
learn how library components are placed into packages. Then you’ll be
able to understand the complete meaning of the access specifiers.
If you want to bring in a single class, you can name that class in the
import statement
import java.util.ArrayList;
Now you can use ArrayList with no qualification. However, none of the
other classes in java.util are available.
So far, most of the examples in this book have existed in a single file and
have been designed for local use, and haven’t bothered with package
names. (In this case the class name is placed in the “default package.”)
This is certainly an option, and for simplicity’s sake this approach will be
used whenever possible throughout the rest of this book. However, if
you’re planning to create libraries or programs that are friendly to other
Java programs on the same machine, you must think about preventing
class name clashes.
When you create a source-code file for Java, it’s commonly called a
compilation unit (sometimes a translation unit). Each compilation unit
must have a name ending in .java, and inside the compilation unit there
can be a public class that must have the same name as the file (including
capitalization, but excluding the .java filename extension). There can be
only one public class in each compilation unit, otherwise the compiler
will complain. The rest of the classes in that compilation unit, if there are
any, are hidden from the world outside that package because they’re not
public, and they comprise “support” classes for the main public class.
When you compile a .java file you get an output file with exactly the same
name but an extension of .class for each class in the .java file. Thus you
can end up with quite a few .class files from a small number of .java
files. If you’ve programmed with a compiled language, you might be used
to the compiler spitting out an intermediate form (usually an “obj” file)
that is then packaged together with others of its kind using a linker (to
create an executable file) or a librarian (to create a library). That’s not
how Java works. A working program is a bunch of .class files, which can
be packaged and compressed into a JAR file (using Java’s jar archiver).
A library is also a bunch of these class files. Each file has one class that is
public (you’re not forced to have a public class, but it’s typical), so
there’s one component for each file. If you want to say that all these
components (that are in their own separate .java and .class files) belong
together, that’s where the package keyword comes in.
For example, suppose the name of the file is MyClass.java. This means
there can be one and only one public class in that file, and the name of
that class must be MyClass (including the capitalization):
package mypackage;
public class MyClass {
// . . .
Now, if someone wants to use MyClass or, for that matter, any of the
other public classes in mypackage, they must use the import keyword
to make the name or names in mypackage available. The alternative is
to give the fully qualified name:
mypackage.MyClass m = new mypackage.MyClass();
1 There’s nothing in Java that forces the use of an interpreter. There exist native-code Java
compilers that generate a single executable file.
Collecting the package files into a single subdirectory solves two other
problems: creating unique package names, and finding those classes that
might be buried in a directory structure someplace. This is accomplished,
as was introduced in Chapter 2, by encoding the path of the location of the
.class file into the name of the package. The compiler enforces this, but
by convention, the first part of the package name is the Internet domain
name of the creator of the class, reversed. Since Internet domain names
are guaranteed to be unique, if you follow this convention it’s guaranteed
that your package name will be unique and thus you’ll never have a
name clash. (That is, until you lose the domain name to someone else who
starts writing Java code with the same path names as you did.) Of course,
if you don’t have your own domain name then you must fabricate an
unlikely combination (such as your first and last name) to create unique
package names. If you’ve decided to start publishing Java code it’s worth
the relatively small effort to get a domain name.
The second part of this trick is resolving the package name into a
directory on your machine, so when the Java program runs and it needs to
load the .class file (which it does dynamically, at the point in the program
There’s a variation when using JAR files, however. You must put the name
of the JAR file in the classpath, not just the path where it’s located. So for
a JAR named grape.jar your classpath would include:
CLASSPATH=.;D:\JAVA\LIB;C:\flavors\grape.jar
Once the classpath is set up properly, the following file can be placed in
any directory:
//: c05:LibTest.java
// Uses the library.
import com.bruceeckel.simple.*;
Setting the CLASSPATH has been such a trial for beginning Java users (it
was for me, when I started) that Sun made the JDK in Java 2 a bit
smarter. You’ll find that, when you install it, even if you don’t set a
CLASSPATH you’ll be able to compile and run basic Java programs. To
compile and run the source-code package for this book (available on the
CD ROM packaged with this book, or at www.BruceEckel.com), however,
you will need to make some modifications to your CLASSPATH (these are
explained in the source-code package).
Collisions
What happens if two libraries are imported via * and they include the
same names? For example, suppose a program does this:
import com.bruceeckel.simple.*;
import java.util.*;
Since java.util.* also contains a Vector class, this causes a potential
collision. However, as long as you don’t write the code that actually causes
the collision, everything is OK—this is good because otherwise you might
end up doing a lot of typing to prevent collisions that would never happen.
public class P {
public static void rint(String s) {
System.out.print(s);
}
public static void rintln(String s) {
System.out.println(s);
}
} ///:~
You can use this shorthand to print a String either with a newline
(P.rintln( )) or without a newline (P.rint( )).
You can guess that the location of this file must be in a directory that
starts at one of the CLASSPATH locations, then continues
com/bruceeckel/tools. After compiling, the P.class file can be used
anywhere on your system with an import statement:
//: c05:ToolTest.java
// Uses the tools library.
import com.bruceeckel.tools.*;
So from now on, whenever you come up with a useful new utility, you can
add it to the tools directory. (Or to your own personal util or tools
directory.)
However, there are other valuable uses for conditional compilation. A very
common use is for debugging code. The debugging features are enabled
during development, and disabled in the shipping product. Allen Holub
(www.holub.com) came up with the idea of using packages to mimic
conditional compilation. He used this to create a Java version of C’s very
useful assertion mechanism, whereby you can say “this should be true” or
“this should be false” and if the statement doesn’t agree with your
assertion you’ll find out about it. Such a tool is quite helpful during
debugging.
When you want to use this class, you add a line in your program:
import com.bruceeckel.tools.debug.*;
To remove the assertions so you can ship the code, a second Assert class
is created, but in a different package:
//: com:bruceeckel:tools:Assert.java
// Turning off the assertion output
// so you can ship the program.
package com.bruceeckel.tools;
Package caveat
It’s worth remembering that anytime you create a package, you implicitly
specify a directory structure when you give the package a name. The
package must live in the directory indicated by its name, which must be a
directory that is searchable starting from the CLASSPATH.
Experimenting with the package keyword can be a bit frustrating at first,
because unless you adhere to the package-name to directory-path rule,
One way or another, everything has some kind of access specified for it. In
the following sections, you’ll learn all about the various types of access,
starting with the default access.
“Friendly”
What if you give no access specifier at all, as in all the examples before
this chapter? The default access has no keyword, but it is commonly
referred to as “friendly.” It means that all the other classes in the current
package have access to the friendly member, but to all the classes outside
of this package the member appears to be private. Since a compilation
unit—a file—can belong only to a single package, all the classes within a
single compilation unit are automatically friendly with each other. Thus,
friendly elements are also said to have package access.
The class controls which code has access to its members. There’s no magic
way to “break in.” Code from another package can’t show up and say, “Hi,
I’m a friend of Bob’s!” and expect to see the protected, friendly, and
private members of Bob. The only way to grant access to a member is to:
2. Make the member friendly by leaving off any access specifier, and
put the other classes in the same package. Then the other classes
can access the member.
class Cake {
public static void main(String[] args) {
class Pie {
void f() { System.out.println("Pie.f()"); }
} ///:~
You might initially view these as completely foreign files, and yet Cake is
able to create a Pie object and call its f( ) method! (Note that you must
have ‘.’ in your CLASSPATH in order for the files to compile.) You’d
typically think that Pie and f( ) are friendly and therefore not available to
Cake. They are friendly—that part is correct. The reason that they are
available in Cake.java is because they are in the same directory and have
no explicit package name. Java treats files like this as implicitly part of the
“default package” for that directory, and therefore friendly to all the other
files in that directory.
class Sundae {
private Sundae() {}
static Sundae makeASundae() {
return new Sundae();
}
}
Any method that you’re certain is only a “helper” method for that class
can be made private, to ensure that you don’t accidentally use it
elsewhere in the package and thus prohibit yourself from changing or
removing the method. Making a method private guarantees that you
retain this option.
2 There’s another effect in this case: Since the default constructor is the only one defined,
and it’s private, it will prevent inheritance of this class. (A subject that will be introduced
in Chapter 6.)
If you create a new package and you inherit from a class in another
package, the only members you have access to are the public members of
the original package. (Of course, if you perform the inheritance in the
same package, you have the normal package access to all the “friendly”
members.) Sometimes the creator of the base class would like to take a
particular member and grant access to derived classes but not the world
in general. That’s what protected does. If you refer back to the file
Cookie.java, the following class cannot access the “friendly” member:
//: c05:ChocolateChip.java
// Can't access friendly member
// in another class.
import c05.dessert.*;
Interface and
implementation
Access control is often referred to as implementation hiding. Wrapping
data and methods within classes in combination with implementation
hiding is often called encapsulation3. The result is a data type with
characteristics and behaviors.
This feeds directly into the second reason, which is to separate the
interface from the implementation. If the structure is used in a set of
programs, but client programmers can’t do anything but send messages to
the public interface, then you can change anything that’s not public
(e.g., “friendly,” protected, or private) without requiring modifications
to client code.
In the original OOP language, Simula-67, the keyword class was used to
describe a new data type. The same keyword has been used for most
object-oriented languages. This is the focal point of the whole language:
the creation of new data types that are more than just boxes containing
data and methods.
For clarity, you might prefer a style of creating classes that puts the
public members at the beginning, followed by the protected, friendly,
and private members. The advantage is that the user of the class can
then read down from the top and see first what’s important to them (the
public members, because they can be accessed outside the file), and stop
reading when they encounter the non-public members, which are part of
the internal implementation:
public class X {
public void pub1( ) { /* . . . */ }
public void pub2( ) { /* . . . */ }
Class access
In Java, the access specifiers can also be used to determine which classes
within a library will be available to the users of that library. If you want a
class to be available to a client programmer, you place the public
keyword somewhere before the opening brace of the class body. This
controls whether the client programmer can even create an object of the
class.
To control the access of a class, the specifier must appear before the
keyword class. Thus you can say:
public class Widget {
Now if the name of your library is mylib any client programmer can
access Widget by saying
import mylib.Widget;
or
import mylib.*;
1. There can be only one public class per compilation unit (file). The
idea is that each compilation unit has a single public interface
represented by that public class. It can have as many supporting
“friendly” classes as you want. If you have more than one public
class inside a compilation unit, the compiler will give you an error
message.
2. The name of the public class must exactly match the name of the
file containing the compilation unit, including capitalization. So for
Widget, the name of the file must be Widget.java, not
widget.java or WIDGET.java. Again, you’ll get a compile-time
error if they don’t agree.
What if you’ve got a class inside mylib that you’re just using to
accomplish the tasks performed by Widget or some other public class in
mylib? You don’t want to go to the bother of creating documentation for
the client programmer, and you think that sometime later you might want
to completely change things and rip out your class altogether, substituting
a different one. To give you this flexibility, you need to ensure that no
client programmers become dependent on your particular
implementation details hidden inside mylib. To accomplish this, you just
leave the public keyword off the class, in which case it becomes friendly.
(That class can be used only within that package.)
4 Actually, an inner class can be private or protected, but that’s a special case. These will
be introduced in Chapter 7.
class Soup {
private Soup() {}
// (1) Allow creation via static method:
public static Soup makeSoup() {
return new Soup();
}
// (2) Create a static object and
// return a reference upon request.
// (The "Singleton" pattern):
private static Soup ps1 = new Soup();
public static Soup access() {
return ps1;
}
public void f() {}
}
The second option uses what’s called a design pattern, which is covered in
Thinking in Patterns with Java, downloadable at www.BruceEckel.com.
This particular pattern is called a “singleton” because it allows only a
single object to ever be created. The object of class Soup is created as a
static private member of Soup, so there’s one and only one, and you
can’t get at it except through the public method access( ).
Without rules, client programmers can do anything they want with all the
members of a class, even if you might prefer they don’t directly
manipulate some of the members. Everything’s naked to the world.
This chapter looked at how classes are built to form libraries; first, the
way a group of classes is packaged within a library, and second, the way
the class controls access to its members.
There are two reasons for controlling access to members. The first is to
keep users’ hands off tools that they shouldn’t touch; tools that are
necessary for the internal machinations of the data type, but not part of
the interface that users need to solve their particular problems. So making
methods and fields private is a service to users because they can easily
see what’s important to them and what they can ignore. It simplifies their
understanding of the class.
The second and most important 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. You might build
a class one way at first, and then discover that restructuring your code will
provide much greater speed. If the interface and implementation are
clearly separated and protected, you can accomplish this without forcing
the user to rewrite their code.
When you have the ability to change the underlying implementation, you
can not only improve your design later, but you also have the freedom to
make mistakes. No matter how carefully you plan and design you’ll make
mistakes. Knowing that it’s relatively safe to make these mistakes means
you’ll be more experimental, you’ll learn faster, and you’ll finish your
project sooner.
The public interface to a class is what the user does see, so that is the most
important part of the class to get “right” during analysis and design. Even
that allows you some leeway for change. If you don’t get the interface right
the first time, you can add more methods, as long as you don’t remove any
that client programmers have already used in their code.
Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in Java
Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
2. In the section labeled “package: the library unit,” turn the code
fragments concerning mypackage into a compiling and running
set of Java files.
10. Create a new directory and edit your CLASSPATH to include that
new directory. Copy the P.class file (produced by compiling
com.bruceeckel.tools.P.java) to your new directory and then
change the names of the file, the P class inside and the method
names. (You might also want to add additional output to watch
how it works.) Create another program in a different directory that
uses your new class.
The trick is to use the classes without soiling the existing code. In this
chapter you’ll see two ways to accomplish this. The first is quite
straightforward: You simply create objects of your existing class inside the
new class. This is called composition, because the new class is composed
of objects of existing classes. You’re simply reusing the functionality of the
code, not its form.
It turns out that much of the syntax and behavior are similar for both
composition and inheritance (which makes sense because they are both
ways of making new types from existing types). In this chapter, you’ll
learn about these code reuse mechanisms.
Composition syntax
Until now, composition has been used quite frequently. You simply place
object references inside new classes. For example, suppose you’d like an
object that holds several String objects, a couple of primitives, and an
271
object of another class. For the nonprimitive objects, you put references
inside your new class, but you define the primitives directly:
//: c06:SprinklerSystem.java
// Composition for code reuse.
class WaterSource {
private String s;
WaterSource() {
System.out.println("WaterSource()");
s = new String("Constructed");
}
public String toString() { return s; }
}
At first glance, you might assume—Java being as safe and careful as it is—
that the compiler would automatically construct objects for each of the
references in the above code; for example, calling the default constructor
for WaterSource to initialize source. The output of the print statement
is in fact:
valve1 = null
valve2 = null
valve3 = null
valve4 = null
i = 0
f = 0.0
source = null
Primitives that are fields in a class are automatically initialized to zero, as
noted in Chapter 2. But the object references are initialized to null, and if
you try to call methods for any of them you’ll get an exception. It’s actually
pretty good (and useful) that you can still print them out without
throwing an exception.
It makes sense that the compiler doesn’t just create a default object for
every reference because that would incur unnecessary overhead in many
cases. If you want the references initialized, you can do it:
1. At the point the objects are defined. This means that they’ll always
be initialized before the constructor is called.
3. Right before you actually need to use the object. This is often called
lazy initialization. It can reduce overhead in situations where the
object doesn’t need to be created every time.
class Soap {
private String s;
Soap() {
System.out.println("Soap()");
s = new String("Constructed");
}
public String toString() { return s; }
}
Inheritance syntax
Inheritance is an integral part of Java (and OOP languages in general). It
turns out that you’re always doing inheritance when you create a class,
because unless you explicitly inherit from some other class, you implicitly
inherit from Java’s standard root class Object.
class Cleanser {
private String s = new String("Cleanser");
public void append(String a) { s += a; }
public void dilute() { append(" dilute()"); }
public void apply() { append(" apply()"); }
public void scrub() { append(" scrub()"); }
public void print() { System.out.println(s); }
public static void main(String[] args) {
Cleanser x = new Cleanser();
x.dilute(); x.apply(); x.scrub();
x.print();
}
}
It’s important that all of the methods in Cleanser are public. Remember
that if you leave off any access specifier the member defaults to “friendly,”
which allows access only to package members. Thus, within this package,
anyone could use those methods if there were no access specifier.
Detergent would have no trouble, for example. However, if a class from
some other package were to inherit from Cleanser it could access only
public members. So to plan for inheritance, as a general rule make all
fields private and all methods public. (protected members also allow
access by derived classes; you’ll learn about this later.) Of course, in
particular cases you must make adjustments, but this is a useful guideline.
When inheriting you’re not restricted to using the methods of the base
class. You can also add new methods to the derived class exactly the way
you put any method in a class: just define it. The method foam( ) is an
example of this.
In Detergent.main( ) you can see that for a Detergent object you can
call all the methods that are available in Cleanser as well as in
Detergent (i.e., foam( )).
class Art {
Art() {
System.out.println("Art constructor");
}
}
Even if you don’t create a constructor for Cartoon( ), the compiler will
synthesize a default constructor for you that calls the base class
constructor.
class Game {
Game(int i) {
System.out.println("Game constructor");
}
}
Combining composition
and inheritance
It is very common to use composition and inheritance together. The
following example shows the creation of a more complex class, using both
inheritance and composition, along with the necessary constructor
initialization:
//: c06:PlaceSetting.java
// Combining composition & inheritance.
class Plate {
Plate(int i) {
System.out.println("Plate constructor");
}
}
class Utensil {
Utensil(int i) {
System.out.println("Utensil constructor");
}
}
Often this is fine, but there are times when your class might perform some
activities during its lifetime that require cleanup. As mentioned in
Chapter 4, you can’t know when the garbage collector will be called, or if it
will be called. So if you want something cleaned up for a class, you must
explicitly write a special method to do it, and make sure that the client
programmer knows that they must call this method. On top of this—as
described in Chapter 10 (“Error Handling with Exceptions”)—you must
guard against an exception by putting such cleanup in a finally clause.
class Shape {
Shape(int i) {
System.out.println("Shape constructor");
}
void cleanup() {
System.out.println("Shape cleanup");
Note that in your cleanup method you must also pay attention to the
calling order for the base-class and member-object cleanup methods in
case one subobject depends on another. In general, you should follow the
same form that is imposed by a C++ compiler on its destructors: First
perform all of the cleanup work specific to your class, in the reverse order
of creation. (In general, this requires that base-class elements still be
viable.) Then call the base-class cleanup method, as demonstrated here.
There can be many cases in which the cleanup issue is not a problem; you
just let the garbage collector do the work. But when you must do it
explicitly, diligence and attention is required.
Name hiding
Only C++ programmers might be surprised by name hiding, since it
works differently in that language. If a Java base class has a method name
that’s overloaded several times, redefining that method name in the
derived class will not hide any of the base-class versions. Thus
overloading works regardless of whether the method was defined at this
level or in a base class:
class Homer {
char doh(char c) {
System.out.println("doh(char)");
return 'd';
}
float doh(float f) {
System.out.println("doh(float)");
return 1.0f;
}
}
class Milhouse {}
class Hide {
public static void main(String[] args) {
Bart b = new Bart();
b.doh(1); // doh(float) used
b.doh('x');
b.doh(1.0f);
b.doh(new Milhouse());
}
} ///:~
As you’ll see in the next chapter, it’s far more common to override
methods of the same name using exactly the same signature and return
type as in the base class. It can be confusing otherwise (which is why C++
disallows it, to prevent you from making what is probably a mistake).
Sometimes it makes sense to allow the class user to directly access the
composition of your new class; that is, to make the member objects
public. The member objects use implementation hiding themselves, so
this is a safe thing to do. When the user knows you’re assembling a bunch
of parts, it makes the interface easier to understand. A car object is a
good example:
//: c06:Car.java
// Composition with public objects.
class Engine {
public void start() {}
public void rev() {}
public void stop() {}
}
class Wheel {
public void inflate(int psi) {}
}
class Window {
public void rollup() {}
public void rolldown() {}
class Door {
public Window window = new Window();
public void open() {}
public void close() {}
}
When you inherit, you take an existing class and make a special version of
it. In general, this means that you’re taking a general-purpose class and
specializing it for a particular need. With a little thought, you’ll see that it
would make no sense to compose a car using a vehicle object—a car
doesn’t contain a vehicle, it is a vehicle. The is-a relationship is expressed
with inheritance, and the has-a relationship is expressed with
composition.
The best tack to take is to leave the data members private—you should
always preserve your right to change the underlying implementation. You
can then allow controlled access to inheritors of your class through
protected methods:
//: c06:Orc.java
// The protected keyword.
import java.util.*;
class Villain {
private int i;
protected int read() { return i; }
protected void set(int ii) { i = ii; }
public Villain(int ii) { i = ii; }
public int value(int m) { return m*i; }
}
It’s rather amazing how cleanly the classes are separated. You don’t even
need the source code for the methods in order to reuse the code. At most,
you just import a package. (This is true for both inheritance and
composition.)
Upcasting
The most important aspect of inheritance is not that it provides methods
for the new class. It’s the relationship expressed between the new class
class Instrument {
public void play() {}
static void tune(Instrument i) {
// ...
i.play();
}
}
Why “upcasting”?
The reason for the term is historical, and based on the way class
inheritance diagrams have traditionally been drawn: with the root at the
top of the page, growing downward. (Of course, you can draw your
diagrams any way you find helpful.) The inheritance diagram for
Wind.java is then:
Instrument
Wind
You can also perform the reverse of upcasting, called downcasting, but
this involves a dilemma that is the subject of Chapter 12.
The following sections discuss the three places where final can be used:
for data, methods, and classes.
Final data
Many programming languages have a way to tell the compiler that a piece
of data is “constant.” A constant is useful for two reasons:
A field that is both static and final has only one piece of storage that
cannot be changed.
class Value {
int i = 1;
}
fd1.print("fd1");
System.out.println("Creating new FinalData");
FinalData fd2 = new FinalData();
fd1.print("fd1");
fd2.print("fd2");
}
} ///:~
Since i1 and VAL_TWO are final primitives with compile-time values,
they can both be used as compile-time constants and are not different in
any important way. VAL_THREE is the more typical way you’ll see such
constants defined: public so they’re usable outside the package, static to
emphasize that there’s only one, and final to say that it’s a constant. Note
that final static primitives with constant initial values (that is, compile-
time constants) are named with all capitals by convention, with words
separated by underscores (This is just like C constants, which is where the
convention originated.) Also note that i5 cannot be known at compile-
time, so it is not capitalized.
Just because something is final doesn’t mean that its value is known at
compile-time. This is demonstrated by initializing i4 and i5 at run-time
using randomly generated numbers. This portion of the example also
shows the difference between making a final value static or non-static.
This difference shows up only when the values are initialized at run-time,
since the compile-time values are treated the same by the compiler. (And
presumably optimized out of existence.) The difference is shown in the
output from one run:
fd1: i4 = 15, i5 = 9
Creating new FinalData
fd1: i4 = 15, i5 = 9
Blank finals
Java allows the creation of blank finals, which are fields that are declared
as final but are not given an initialization value. In all cases, the blank
final must be initialized before it is used, and the compiler ensures this.
However, blank finals provide much more flexibility in the use of the
final keyword since, for example, a final field inside a class can now be
different for each object and yet it retains its immutable quality. Here’s an
example:
//: c06:BlankFinal.java
// "Blank" final data members.
class Poppet { }
class BlankFinal {
final int i = 0; // Initialized final
final int j; // Blank final
final Poppet p; // Blank final reference
// Blank finals MUST be initialized
// in the constructor:
BlankFinal() {
j = 1; // Initialize blank final
p = new Poppet();
}
Final arguments
Java allows you to make arguments final by declaring them as such in the
argument list. This means that inside the method you cannot change what
the argument reference points to:
//: c06:FinalArguments.java
// Using "final" with method arguments.
class Gizmo {
public void spin() {}
}
Final methods
There are two reasons for final methods. The first is to put a “lock” on the
method to prevent any inheriting class from changing its meaning. This is
done for design reasons when you want to make sure that a method’s
behavior is retained during inheritance and cannot be overridden.
The second reason for final methods is efficiency. If you make a method
final, you are allowing the compiler to turn any calls to that method into
inline calls. When the compiler sees a final method call it can (at its
discretion) skip the normal approach of inserting code to perform the
method call mechanism (push arguments on the stack, hop over to the
method code and execute it, hop back and clean off the stack arguments,
and deal with the return value) and instead replace the method call with a
copy of the actual code in the method body. This eliminates the overhead
of the method call. Of course, if a method is big, then your code begins to
bloat and you probably won’t see any performance gains from inlining,
since any improvements will be dwarfed by the amount of time spent
inside the method. It is implied that the Java compiler is able to detect
these situations and choose wisely whether to inline a final method.
However, it’s better to not trust that the compiler is able to do this and
make a method final only if it’s quite small or if you want to explicitly
prevent overriding.
This issue can cause confusion, because if you try to override a private
method (which is implicitly final) it seems to work:
//: c06:FinalOverridingIllusion.java
// It only looks like you can override
// a private or private final method.
class WithFinals {
// Identical to "private" alone:
private final void f() {
System.out.println("WithFinals.f()");
}
// Also automatically "final":
private void g() {
System.out.println("WithFinals.g()");
}
}
class OverridingPrivate2
extends OverridingPrivate {
public final void f() {
System.out.println("OverridingPrivate2.f()");
}
public void g() {
System.out.println("OverridingPrivate2.g()");
}
}
Final classes
When you say that an entire class is final (by preceding its definition with
the final keyword), you state that you don’t want to inherit from this class
or allow anyone else to do so. In other words, for some reason the design
of your class is such that there is never a need to make any changes, or for
safety or security reasons you don’t want subclassing. Alternatively, you
might be dealing with an efficiency issue, and you want to make sure that
any activity involved with objects of this class are as efficient as possible.
//: c06:Jurassic.java
// Making an entire class final.
You can add the final specifier to a method in a final class, but it doesn’t
add any meaning.
Final caution
It can seem to be sensible to make a method final while you’re designing
a class. You might feel that efficiency is very important when using your
class and that no one could possibly want to override your methods
anyway. Sometimes this is true.
The standard Java library is a good example of this. In particular, the Java
1.0/1.1 Vector class was commonly used and might have been even more
useful if, in the name of efficiency, all the methods hadn’t been made
final. It’s easily conceivable that you might want to inherit and override
with such a fundamentally useful class, but the designers somehow
decided this wasn’t appropriate. This is ironic for two reasons. First,
Stack is inherited from Vector, which says that a Stack is a Vector,
which isn’t really true from a logical standpoint. Second, many of the most
important methods of Vector, such as addElement( ) and
elementAt( ) are synchronized. As you will see in Chapter 14, this
incurs a significant performance overhead that probably wipes out any
gains provided by final. This lends credence to the theory that
programmers are consistently bad at guessing where optimizations should
occur. It’s just too bad that such a clumsy design made it into the standard
library where we must all cope with it. (Fortunately, the Java 2 container
library replaces Vector with ArrayList, which behaves much more
civilly. Unfortunately, there’s still plenty of new code being written that
uses the old container library.)
The point of first use is also where the static initialization takes place. All
the static objects and the static code block will be initialized in textual
order (that is, the order that you write them down in the class definition)
at the point of loading. The statics, of course, are initialized only once.
class Insect {
int i = 9;
int j;
Insect() {
prt("i = " + i + ", j = " + j);
If the base class has a base class, that second base class would then be
loaded, and so on. Next, the static initialization in the root base class (in
this case, Insect) is performed, and then the next derived class, and so
on. This is important because the derived-class static initialization might
depend on the base class member being initialized properly.
At this point, the necessary classes have all been loaded so the object can
be created. First, all the primitives in this object are set to their default
values and the object references are set to null—this happens in one fell
swoop by setting the memory in the object to binary zero. Then the base-
class constructor will be called. In this case the call is automatic, but you
can also specify the base-class constructor call (as the first operation in
the Beetle( ) constructor) using super. The base class construction goes
through the same process in the same order as the derived-class
constructor. After the base-class constructor completes, the instance
variables are initialized in textual order. Finally, the rest of the body of the
constructor is executed.
Summary
Both inheritance and composition allow you to create a new type from
existing types. Typically, however, you use composition to reuse existing
types as part of the underlying implementation of the new type, and
inheritance when you want to reuse the interface. Since the derived class
has the base-class interface, it can be upcast to the base, which is critical
for polymorphism, as you’ll see in the next chapter.
Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in Java
Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
5. Take the file Cartoon.java and comment out the constructor for
the Cartoon class. Explain what happens.
6. Take the file Chess.java and comment out the constructor for the
Chess class. Explain what happens.
8. Prove that the base-class constructors are (a) always called, and
(b) called before derived-class constructors.
16. Create a class called Amphibian. From this, inherit a class called
Frog. Put appropriate methods in the base class. In main( ),
create a Frog and upcast it to Amphibian, and demonstrate that
all the methods still work.
18. Create a class with a static final field and a final field and
demonstrate the difference between the two.
20. Create a class with a final method. Inherit from that class and
attempt to override that method.
22. Prove that class loading takes place only once. Prove that loading
may be caused by either the creation of the first instance of that
class, or the access of a static member.
Upcasting revisited
In Chapter 6 you saw how an object can be used as its own type or as an
object of its base type. Taking an object reference and treating it as a
311
reference to its base type is called upcasting, because of the way
inheritance trees are drawn with the base class at the top.
class Note {
private int value;
private Note(int val) { value = val; }
public static final Note
MIDDLE_C = new Note(0),
C_SHARP = new Note(1),
B_FLAT = new Note(2);
} // Etc.
class Instrument {
public void play(Note n) {
System.out.println("Instrument.play()");
}
}
class Note {
private int value;
private Note(int val) { value = val; }
public static final Note
MIDDLE_C = new Note(0),
C_SHARP = new Note(1),
B_FLAT = new Note(2);
} // Etc.
class Instrument {
public void play(Note n) {
System.out.println("Instrument.play()");
}
}
Wouldn’t it be much nicer if you could just write a single method that
takes the base class as its argument, and not any of the specific derived
classes? That is, wouldn’t it be nice if you could forget that there are
derived classes, and write your code to talk only to the base class?
The twist
The difficulty with Music.java can be seen by running the program. The
output is Wind.play( ). This is clearly the desired output, but it doesn’t
seem to make sense that it would work that way. Look at the tune( )
method:
public static void tune(Instrument i) {
// ...
i.play(Note.MIDDLE_C);
}
It receives an Instrument reference. So how can the compiler possibly
know that this Instrument reference points to a Wind in this case and
not a Brass or Stringed? The compiler can’t. To get a deeper
understanding of the issue, it’s helpful to examine the subject of binding.
Method-call binding
Connecting a method call to a method body is called binding. When
binding is performed before the program is run (by the compiler and
linker, if there is one), it’s called early binding. You might not have heard
the term before because it has never been an option with procedural
languages. C compilers have only one kind of method call, and that’s early
binding.
The solution is called late binding, which means that the binding occurs
at run-time based on the type of object. Late binding is also called
dynamic binding or run-time binding. When a language implements late
binding, there must be some mechanism to determine the type of the
object at run-time and to call the appropriate method. That is, the
compiler still doesn’t know the object type, but the method-call
mechanism finds out and calls the correct method body. The late-binding
mechanism varies from language to language, but you can imagine that
some sort of type information must be installed in the objects.
All method binding in Java uses late binding unless a method has been
declared final. This means that ordinarily you don’t need to make any
decisions about whether late binding will occur—it happens
automatically.
Why would you declare a method final? As noted in the last chapter, it
prevents anyone from overriding that method. Perhaps more important, it
effectively “turns off” dynamic binding, or rather it tells the compiler that
dynamic binding isn’t necessary. This allows the compiler to generate
slightly more efficient code for final method calls. However, in most
cases it won’t make any overall performance difference in your program,
so it’s best to only use final as a design decision, and not as an attempt to
improve performance.
Suppose you call one of the base-class methods (that have been
overridden in the derived classes):
s.draw();
Again, you might expect that Shape’s draw( ) is called because this is,
after all, a Shape reference—so how could the compiler know to do
anything else? And yet the proper Circle.draw( ) is called because of late
binding (polymorphism).
class Shape {
void draw() {}
void erase() {}
}
Extensibility
Now let’s return to the musical instrument example. Because of
polymorphism, you can add as many new types as you want to the system
without changing the tune( ) method. In a well-designed OOP program,
most or all of your methods will follow the model of tune( ) and
communicate only with the base-class interface. Such a program is
extensible because you can add new functionality by inheriting new data
types from the common base class. The methods that manipulate the
base-class interface will not need to be changed at all to accommodate the
new classes.
Consider what happens if you take the instrument example and add more
methods in the base class and a number of new classes. Here’s the
diagram:
void play()
String what()
void adjust()
Woodwind Brass
All these new classes work correctly with the old, unchanged tune( )
method. Even if tune( ) is in a separate file and new methods are added
to the interface of Instrument, tune( ) works correctly without
recompilation. Here is the implementation of the above diagram:
//: c07:music3:Music3.java
// An extensible program.
import java.util.*;
class Instrument {
public void play() {
System.out.println("Instrument.play()");
}
public String what() {
return "Instrument";
}
public void adjust() {}
In main( ), when you place something inside the Instrument array you
automatically upcast to Instrument.
You can see that the tune( ) method is blissfully ignorant of all the code
changes that have happened around it, and yet it works correctly. This is
exactly what polymorphism is supposed to provide. Your code changes
don’t cause damage to parts of the program that should not be affected.
Put another way, polymorphism is one of the most important techniques
that allow the programmer to “separate the things that change from the
things that stay the same.”
class NoteX {
public static final int
MIDDLE_C = 0, C_SHARP = 1, C_FLAT = 2;
}
class InstrumentX {
public void play(int NoteX) {
System.out.println("InstrumentX.play()");
}
}
Abstract classes
and methods
In all the instrument examples, the methods in the base class
Instrument were always “dummy” methods. If these methods are ever
called, you’ve done something wrong. That’s because the intent of
Instrument is to create a common interface for all the classes derived
from it.
If you have an abstract class like Instrument, objects of that class almost
always have no meaning. That is, Instrument is meant to express only
the interface, and not a particular implementation, so creating an
Instrument object makes no sense, and you’ll probably want to prevent
the user from doing it. This can be accomplished by making all the
methods in Instrument print error messages, but that delays the
information until run-time and requires reliable exhaustive testing on the
user’s part. It’s always better to catch problems at compile-time.
Java provides a mechanism for doing this called the abstract method1.
This is a method that is incomplete; it has only a declaration and no
method body. Here is the syntax for an abstract method declaration:
abstract void f();
A class containing abstract methods is called an abstract class. If a class
contains one or more abstract methods, the class must be qualified as
abstract. (Otherwise, the compiler gives you an error message.)
If you inherit from an abstract class and you want to make objects of the
new type, you must provide method definitions for all the abstract
methods in the base class. If you don’t (and you may choose not to), then
1 For C++ programmers, this is the analogue of C++’s pure virtual function.
The Instrument class can easily be turned into an abstract class. Only
some of the methods will be abstract, since making a class abstract
doesn’t force you to make all the methods abstract. Here’s what it looks
like:
abstract Instrument
extends extends
Woodwind Brass
It’s helpful to create abstract classes and methods because they make the
abstractness of a class explicit, and tell both the user and the compiler
how it was intended to be used.
A constructor for the base class is always called in the constructor for a
derived class, chaining up the inheritance hierarchy so that a constructor
for every base class is called. This makes sense because the constructor
has a special job: to see that the object is built properly. A derived class
has access to its own members only, and not to those of the base class
(whose members are typically private). Only the base-class constructor
has the proper knowledge and access to initialize its own elements.
Therefore, it’s essential that all constructors get called, otherwise the
entire object wouldn’t be constructed. That’s why the compiler enforces a
constructor call for every portion of a derived class. It will silently call the
default constructor if you don’t explicitly call a base-class constructor in
the derived-class constructor body. If there is no default constructor, the
compiler will complain. (In the case where a class has no constructors, the
compiler will automatically synthesize a default constructor.)
class Meal {
Meal() { System.out.println("Meal()"); }
class Bread {
Bread() { System.out.println("Bread()"); }
}
class Cheese {
Cheese() { System.out.println("Cheese()"); }
}
class Lettuce {
Lettuce() { System.out.println("Lettuce()"); }
}
The order of the constructor calls is important. When you inherit, you
know all about the base class and can access any public and protected
members of the base class. This means that you must be able to assume
that all the members of the base class are valid when you’re in the derived
class. In a normal method, construction has already taken place, so all the
members of all parts of the object have been built. Inside the constructor,
however, you must be able to assume that all members that you use have
been built. The only way to guarantee this is for the base-class constructor
to be called first. Then when you’re in the derived-class constructor, all
the members you can access in the base class have been initialized.
“Knowing that all members are valid” inside the constructor is also the
reason that, whenever possible, you should initialize all member objects
(that is, objects placed in the class using composition) at their point of
definition in the class (e.g., b, c, and l in the example above). If you follow
this practice, you will help ensure that all base class members and
member objects of the current object have been initialized. Unfortunately,
this doesn’t handle every case, as you will see in the next section.
class DoBaseFinalization {
public static boolean flag = false;
}
class Characteristic {
String s;
Characteristic(String c) {
s = c;
System.out.println(
"Creating Characteristic " + s);
}
protected void finalize() {
System.out.println(
"finalizing Characteristic " + s);
}
}
class LivingCreature {
Characteristic p =
new Characteristic("is alive");
LivingCreature() {
System.out.println("LivingCreature()");
}
protected void finalize() throws Throwable {
System.out.println(
"LivingCreature finalize");
This is not exactly the case. If you call a dynamically bound method inside
a constructor, the overridden definition for that method is used. However,
the effect can be rather unexpected, and can conceal some difficult-to-find
bugs.
On the other hand, you should be pretty horrified at the outcome of this
program. You’ve done a perfectly logical thing, and yet the behavior is
mysteriously wrong, with no complaints from the compiler. (C++
produces more rational behavior in this situation.) Bugs like this could
easily be buried and take a long time to discover.
class Stage {
Actor a = new HappyActor();
void change() { a = new SadActor(); }
void go() { a.act(); }
}
Shape
draw()
erase()
That is, the base class can receive any message you can send to the
derived class because the two have exactly the same interface. All you
need to do is upcast from the derived class and never look back to see
what exact type of object you’re dealing with. Everything is handled
through polymorphism.
When you see it this way, it seems like a pure “is-a” relationship is the
only sensible way to do things, and any other design indicates muddled
thinking and is by definition broken. This too is a trap. As soon as you
start thinking this way, you’ll turn around and discover that extending the
interface (which, unfortunately, the keyword extends seems to
encourage) is the perfect solution to a particular problem. This could be
termed an “is-like-a” relationship because the derived class is like the base
class—it has the same fundamental interface—but it has other features
that require additional methods to implement:
}
void f()
represents a big
void g()
interface
MoreUseful "Is-like-a"
void f()
void g()
}
void u()
void v() Extending
the interface
void w()
Talks to Useful
Useful part
object Message
MoreUseful
part
If you’re not upcasting in this case, it won’t bother you, but often you’ll get
into a situation in which you need to rediscover the exact type of the
object so you can access the extended methods of that type. The following
section shows how this is done.
Useful
Assume this
}
void f()
represents a big
void g()
interface
MoreUseful "Is-like-a"
void f()
void g()
}
void u()
void v() Extending
the interface
void w()
class Useful {
public void f() {}
There’s more to RTTI than a simple cast. For example, there’s a way to see
what type you’re dealing with before you try to downcast it. All of Chapter
Summary
Polymorphism means “different forms.” In object-oriented programming,
you have the same face (the common interface in the base class) and
different forms using that face: the different versions of the dynamically
bound methods.
Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in Java
Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
15. Create an abstract class with no methods. Derive a class and add
a method. Create a static method that takes a reference to the
base class, downcasts it to the derived class, and calls the method.
In main( ), demonstrate that it works. Now put the abstract
declaration for the method in the base class, thus eliminating the
need for the downcast.
In Chapter 7, you learned about the abstract keyword, which allows you
to create one or more methods in a class that have no definitions—you
provide part of the interface without providing a corresponding
implementation, which is created by inheritors. The interface keyword
produces a completely abstract class, one that provides no
implementation at all. You’ll learn that the interface is more than just an
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. It
takes some time to become comfortable with design using inner classes.
Interfaces
The interface keyword takes the abstract concept one step further. You
could think of it as a “pure” abstract class. It allows the creator to
establish the form for a class: method names, argument lists, and return
types, but no method bodies. An interface can also contain fields, but
349
these are implicitly static and final. An interface provides only a form,
but no implementation.
An interface says: “This is what all classes that implement this particular
interface will look like.” Thus, any code that uses a particular interface
knows what methods might be called for that interface, and that’s all. So
the interface is used to establish a “protocol” between classes. (Some
object-oriented programming languages have a keyword called protocol
to do the same thing.)
void play();
String what();
void adjust();
extends extends
Woodwind Brass
You can see this in the modified version of the Instrument example.
Note that every method in the interface is strictly a declaration, which is
the only thing the compiler allows. In addition, none of the methods in
Instrument are declared as public, but they’re automatically public
anyway:
//: c08:music5:Music5.java
interface Instrument {
// Compile-time constant:
int i = 5; // static & final
// Cannot have method definitions:
void play(); // Automatically public
String what();
void adjust();
}
In a derived class, you aren’t forced to have a base class that is either an
abstract or “concrete” (one with no abstract methods). If you do inherit
from a non-interface, you can inherit from only one. All the rest of the
base elements must be interfaces. You place all the interface names after
the implements keyword and separate them with commas. You can have
as many interfaces as you want—each one becomes an independent type
that you can upcast to. The following example shows a concrete class
combined with several interfaces to produce a new class:
//: c08:Adventure.java
// Multiple interfaces.
import java.util.*;
interface CanSwim {
void swim();
}
interface CanFly {
void fly();
}
class ActionCharacter {
public void fight() {}
}
In class Adventure, you can see that there are four methods that take as
arguments the various interfaces and the concrete class. When a Hero
object is created, it can be passed to any of these methods, which means it
is being upcast to each interface in turn. Because of the way interfaces
are designed in Java, this works without a hitch and without any
particular effort on the part of the programmer.
Keep in mind that the core reason for interfaces is shown in the above
example: to be able to upcast to more than one base type. However, a
second reason for using interfaces is the same as using an abstract base
class: to prevent the client programmer from making an object of this
class and to establish that it is only an interface. This brings up a
question: Should you use an interface or an abstract class? An
interface gives you the benefits of an abstract class and the benefits of
an interface, so if it’s possible to create your base class without any
method definitions or member variables you should always prefer
interfaces to abstract classes. In fact, if you know something is going to
be a base class, your first choice should be to make it an interface, and
only if you’re forced to have method definitions or member variables
should you change to an abstract class, or if necessary a concrete class.
interface Monster {
void menace();
}
interface Lethal {
void kill();
}
interface Vampire
extends DangerousMonster, Lethal {
void drinkBlood();
}
class HorrorShow {
static void u(Monster b) { b.menace(); }
static void v(DangerousMonster d) {
d.menace();
d.destroy();
}
public static void main(String[] args) {
Grouping constants
Because any fields you put into an interface are automatically static and
final, the interface is a convenient tool for creating groups of constant
values, much as you would with an enum in C or C++. For example:
//: c08:Months.java
// Using interfaces to create groups of constants.
package c08;
Now you can use the constants from outside the package by importing
c08.* or c08.Months just as you would with any other package, and
If you do want extra type safety, you can build a class like this1:
//: c08:Month2.java
// A more robust enumeration system.
package c08;
Nesting interfaces
2Interfaces may be nested within classes and within other interfaces. This
reveals a number of very interesting features:
//: c08:NestingInterfaces.java
class A {
interface B {
void f();
}
public class BImp implements B {
public void f() {}
}
private class BImp2 implements B {
public void f() {}
}
public interface C {
void f();
}
interface E {
interface G {
void f();
}
// Redundant "public":
public interface H {
void f();
}
void g();
// Cannot be private within an interface:
//! private interface I {}
}
As a new twist, interfaces can also be private as seen in A.D (the same
qualification syntax is used for nested interfaces as for nested classes).
Initially, these features may seem like they are added strictly for syntactic
consistency, but I generally find that once you know about a feature, you
often discover places where it is useful.
Inner classes
It’s possible to place a class definition within another class definition. This
is called an inner class. The inner class is a valuable feature because it
allows you to group classes that logically belong together and to control
Often, while you’re learning about them, the need for inner classes isn’t
immediately obvious. At the end of this section, after all of the syntax and
semantics of inner classes have been described, you’ll find examples that
should make clear the benefits of inner classes.
You create an inner class just as you’d expect—by placing the class
definition inside a surrounding class:
//: c08:Parcel1.java
// Creating inner classes.
More typically, an outer class will have a method that returns a reference
to an inner class, like this:
//: c08:Parcel2.java
// Returning a reference to an inner class.
However, inner classes really come into their own when you start
upcasting to a base class, and in particular to an interface. (The effect of
producing an interface reference from an object that implements it is
essentially the same as upcasting to a base class.) That’s because the inner
class—the implementation of the interface—can then be completely
unseen and unavailable to anyone, which is convenient for hiding the
implementation. All you get back is a reference to the base class or the
interface.
First, the common interfaces will be defined in their own files so they can
be used in all the examples:
//: c08:Destination.java
public interface Destination {
String readLabel();
} ///:~
//: c08:Contents.java
public interface Contents {
int value();
} ///:~
Now Contents and Destination represent interfaces available to the
client programmer. (The interface, remember, automatically makes all
of its members public.)
When you get back a reference to the base class or the interface, it’s
possible that you can’t even find out the exact type, as shown here:
//: c08:Parcel3.java
// Returning a reference to an inner class.
class Test {
public static void main(String[] args) {
Parcel3 p = new Parcel3();
Contents c = p.cont();
Destination d = p.dest("Tanzania");
// Illegal -- can't access private class:
//! Parcel3.PContents pc = p.new PContents();
}
} ///:~
Note that since main( ) is in Test, when you want to run this program
you don’t execute Parcel3, but instead:
java Test
In the example, main( ) must be in a separate class in order to
demonstrate the privateness of the inner class PContents.
In Parcel3, something new has been added: the inner class PContents
is private so no one but Parcel3 can access it. PDestination is
Inner classes
in methods and scopes
What you’ve seen so far encompasses the typical use for inner classes. In
general, the code that you’ll write and read involving inner classes will be
“plain” inner classes that are simple and easy to understand. However, the
design for inner classes is quite complete and there are a number of other,
more obscure, ways that you can use them if you choose: inner classes can
be created within a method or even an arbitrary scope. There are two
reasons for doing this:
The first example shows the creation of an entire class within the scope of
a method (instead of the scope of another class):
//: c08:Parcel4.java
// Nesting a class within a method.
The next example shows how you can nest an inner class within any
arbitrary scope:
//: c08:Parcel5.java
// Nesting a class within a scope.
As long as you’re simply assigning a field, the above approach is fine. But
what if you need to perform some constructor-like activity? With instance
initialization, you can, in effect, create a constructor for an anonymous
inner class:
//: c08:Parcel9.java
interface Selector {
boolean end();
Object current();
void next();
}
3 This is very different from the design of nested classes in C++, which is simply a name-
hiding mechanism. There is no link to an enclosing object and no implied permissions in
C++.
At first, the creation of SSelector looks like just another inner class. But
examine it closely. Note that each of the methods end( ), current( ), and
next( ) refer to obs, which is a reference that isn’t part of SSelector, but
is instead a private field in the enclosing class. However, the inner class
can access methods and fields from the enclosing class as if they owned
them. This turns out to be very convenient, as you can see in the above
example.
static inner classes are different than non-static inner classes in another
way, as well. Fields and methods in non-static inner classes can only be
at the outer level of a class, so non-static inner classes cannot have static
data, static fields, or static inner classes. However, static inner classes
can have all of these:
//: c08:Parcel10.java
// Static inner classes.
As you will see shortly, in an ordinary (non-static) inner class, the link to
the outer class object is achieved with a special this reference. A static
inner class does not have this special this reference, which makes it
analogous to a static method.
Normally you can’t put any code inside an interface, but a static inner
class can be part of an interface. Since the class is static it doesn’t
violate the rules for interfaces—the static inner class is only placed inside
the namespace of the interface:
interface IInterface {
static class Inner {
int i, j, k;
public Inner() {}
void f() {}
}
} ///:~
Earlier in this book I suggested putting a main( ) in every class to act as a
test bed for that class. One drawback to this is the amount of extra
compiled code you must carry around. If this is a problem, you can use a
static inner class to hold your test code:
//: c08:TestBed.java
// Putting test code in a static inner class.
class TestBed {
TestBed() {}
void f() { System.out.println("f()"); }
public static class Tester {
public static void main(String[] args) {
TestBed t = new TestBed();
t.f();
}
}
} ///:~
This generates a separate class called TestBed$Tester (to run the
program, you say java TestBed$Tester). You can use this class for
testing, but you don’t need to include it in your shipping product.
Sometimes you want to tell some other object to create an object of one of
its inner classes. To do this you must provide a reference to the other
outer class object in the new expression, like this:
//: c08:Parcel11.java
// Creating instances of inner classes.
class MNA {
private void f() {}
class A {
private void g() {}
public class B {
void h() {
g();
f();
}
}
}
}
class WithInner {
class Inner {}
}
class Egg {
protected class Yolk {
public Yolk() {
System.out.println("Egg.Yolk()");
}
}
private Yolk y;
public Egg() {
System.out.println("New Egg()");
y = new Yolk();
}
}
class Egg2 {
protected class Yolk {
public Yolk() {
System.out.println("Egg2.Yolk()");
}
public void f() {
System.out.println("Egg2.Yolk.f()");
}
}
private Yolk y = new Yolk();
public Egg2() {
System.out.println("New Egg2()");
}
public void insertYolk(Yolk yy) { y = yy; }
public void g() { y.f(); }
}
A question that cuts to the heart of inner classes is this: if I just need a
reference to an interface, why don’t I just make the outer class
implement that interface? The answer is “If that’s all you need, then
that’s how you should do it.” So what is it that distinguishes an inner class
implementing an interface from an outer class implementing the same
interface? The answer is that you can’t always have the convenience of
interfaces—sometimes you’re working with implementations. So the
most compelling reason for inner classes is:
5 On the other hand, ‘$’ is a meta-character to the Unix shell and so you’ll sometimes have
trouble when listing the .class files. This is a bit strange coming from Sun, a Unix-based
company. My guess is that they weren’t considering this issue, but instead thought you’d
naturally focus on the source-code files.
To see this in more detail, consider a situation where you have two
interfaces that must somehow be implemented within a class. Because of
the flexibility of interfaces, you have two choices: a single class or an inner
class:
//: c08:MultiInterfaces.java
// Two ways that a class can
// implement multiple interfaces.
interface A {}
interface B {}
class X implements A, B {}
class Y implements A {
B makeB() {
// Anonymous inner class:
return new B() {};
}
}
class C {}
abstract class D {}
class Z extends C {
D makeD() { return new D() {}; }
}
1. The inner class can have multiple instances, each with its own state
information that is independent of the information in the outer
class object.
3. The point of creation of the inner class object is not tied to the
creation of the outer class object.
The closure provided by the inner class is a perfect solution; more flexible
and far safer than a pointer. Here’s a simple example:
//: c08:Callbacks.java
// Using inner classes for callbacks
class MyIncrement {
public void increment() {
System.out.println("Other operation");
}
public static void f(MyIncrement mi) {
mi.increment();
}
}
class Caller {
private Incrementable callbackReference;
To see how inner classes allow the simple creation and use of control
frameworks, consider a control framework whose job is to execute events
whenever those events are “ready.” Although “ready” could mean
anything, in this case the default will be based on clock time. What follows
is a control framework that contains no specific information about what
it’s controlling. First, here is the interface that describes any control event.
It’s an abstract class instead of an actual interface because the default
action( ) is the method that’s called when the Event is ready( ), and
description( ) gives textual information about the Event.
The following file contains the actual control framework that manages
and fires events. The first class is really just a “helper” class whose job is
to hold Event objects. You can replace it with any appropriate container,
and in Chapter 9 you’ll discover other containers that will do the trick
without requiring you to write this extra code:
//: c08:controller:Controller.java
// Along with Event, the generic
// framework for all control systems:
package c08.controller;
Note that removeCurrent( ) doesn’t just set some flag indicating that
the object is no longer in use. Instead, it sets the reference to null. This is
important because if the garbage collector sees a reference that’s still in
use then it can’t clean up the object. If you think your references might
hang around (as they would here), then it’s a good idea to set them to null
to give the garbage collector permission to clean them up.
Note that so far in this design you know nothing about exactly what an
Event does. And this is the crux of the design; how it “separates the
things that change from the things that stay the same.” Or, to use my
term, the “vector of change” is the different actions of the various kinds of
Event objects, and you express different actions by creating different
Event subclasses.
This is where inner classes come into play. They allow two things:
6 For some reason this has always been a pleasing problem for me to solve; it came from
my earlier book C++ Inside & Out, but Java allows a much more elegant solution.
Summary
Interfaces and inner classes are more sophisticated concepts than what
you’ll find in many OOP languages. For example, there’s nothing like
them in C++. Together, they solve the same problem that C++ attempts to
solve with its multiple inheritance (MI) feature. However, MI in C++
turns out to be rather difficult to use, while Java interfaces and inner
classes are, by comparison, much more accessible.
13. Create an interface with at least one method, and implement that
interface by defining an inner class within a method, which
returns a reference to your interface.
14. Repeat Exercise 13 but define the inner class within a scope within
a method.
18. Create a class with a private field and a private method. Create
an inner class with a method that modifies the outer class field and
calls the outer class method. In a second outer class method,
create an object of the inner class and call it’s method, then show
the effect on the outer class object.
29. Show that an inner class has access to the private elements of its
outer class. Determine whether the reverse is true.
To solve this rather essential problem, Java has several ways to hold
objects (or rather, references to objects). The built-in type is the array,
which has been discussed before. Also, the Java utilities library has a
reasonably complete set of container classes (also known as collection
classes, but because the Java 2 libraries use the name Collection to refer
to a particular subset of the library, I shall use the more inclusive term
“container”). Containers provide sophisticated ways to hold and even
manipulate your objects.
Arrays
Most of the necessary introduction to arrays is in the last section of
Chapter 4, which showed how you define and initialize an array. Holding
objects is the focus of this chapter, and an array is just one way to hold
objects. But there are a number of other ways to hold objects, so what
makes an array special?
407
There are two issues that distinguish arrays from other types of
containers: efficiency and type. The array is the most efficient way that
Java provides to store and randomly access a sequence of objects
(actually, object references). The array is a simple linear sequence, which
makes element access fast, but you pay for this speed: when you create an
array object, its size is fixed and cannot be changed for the lifetime of that
array object. You might suggest creating an array of a particular size and
then, if you run out of space, creating a new one and moving all the
references from the old one to the new one. This is the behavior of the
ArrayList class, which will be studied later in this chapter. However,
because of the overhead of this size flexibility, an ArrayList is
measurably less efficient than an array.
The vector container class in C++ does know the type of objects it holds,
but it has a different drawback when compared with arrays in Java: the
C++ vector’s operator[] doesn’t do bounds checking, so you can run
past the end1. In Java, you get bounds checking regardless of whether
you’re using an array or a container—you’ll get a RuntimeException if
you exceed the bounds. As you’ll learn in Chapter 10, this type of
exception indicates a programmer error, and thus you don’t need to check
for it in your code. As an aside, the reason the C++ vector doesn’t check
bounds with every access is speed—in Java you have the constant
performance overhead of bounds checking all the time for both arrays and
containers.
The other generic container classes that will be studied in this chapter,
List, Set, and Map, all deal with objects as if they had no specific type.
That is, they treat them as type Object, the root class of all classes in
Java. This works fine from one standpoint: you need to build only one
container, and any Java object will go into that container. (Except for
primitives—these can be placed in containers as constants using the Java
primitive wrapper classes, or as changeable values by wrapping in your
own class.) This is the second place where an array is superior to the
generic containers: when you create an array, you create it to hold a
specific type. This means that you get compile-time type checking to
1 It’s possible, however, to ask how big the vector is, and the at( ) method does perform
bounds checking.
For efficiency and type checking it’s always worth trying to use an array if
you can. However, when you’re trying to solve a more general problem
arrays can be too restrictive. After looking at arrays, the rest of this
chapter will be devoted to the container classes provided by Java.
The following example shows the various ways that an array can be
initialized, and how the array references can be assigned to different array
objects. It also shows that arrays of objects and arrays of primitives are
almost identical in their use. The only difference is that arrays of objects
hold references, while arrays of primitives hold the primitive values
directly.
//: c09:ArraySize.java
// Initialization & re-assignment of arrays.
// Arrays of primitives:
int[] e; // Null reference
int[] f = new int[5];
int[] g = new int[4];
for(int i = 0; i < g.length; i++)
g[i] = i*i;
int[] h = { 11, 47, 93 };
// Compile error: variable e not initialized:
//!System.out.println("e.length=" + e.length);
System.out.println("f.length = " + f.length);
// The primitives inside the array are
// automatically initialized to zero:
for(int i = 0; i < f.length; i++)
System.out.println("f[" + i + "]=" + f[i]);
System.out.println("g.length = " + g.length);
System.out.println("h.length = " + h.length);
e = h;
System.out.println("e.length = " + e.length);
e = new int[] { 1, 2 };
The expression
a = d;
shows how you can take a reference that’s attached to one array object
and assign it to another array object, just as you can do with any other
type of object reference. Now both a and d are pointing to the same array
object on the heap.
The second part of ArraySize.java shows that primitive arrays work just
like object arrays except that primitive arrays hold the primitive values
directly.
Containers of primitives
Container classes can hold only references to objects. An array, however,
can be created to hold primitives directly, as well as references to objects.
It is possible to use the “wrapper” classes such as Integer, Double, etc.
to place primitive values inside a container, but the wrapper classes for
primitives can be awkward to use. In addition, it’s much more efficient to
Of course, if you’re using a primitive type and you need the flexibility of a
container that automatically expands when more space is needed, the
array won’t work and you’re forced to use a container of wrapped
primitives. You might think that there should be a specialized type of
ArrayList for each of the primitive data types, but Java doesn’t provide
this for you. Some sort of templatizing mechanism might someday
provide a better way for Java to handle this problem.2
Returning an array
Suppose you’re writing a method and you don’t just want to return just
one thing, but a whole bunch of things. Languages like C and C++ make
this difficult because you can’t just return an array, only a pointer to an
array. This introduces problems because it becomes messy to control the
lifetime of the array, which easily leads to memory leaks.
Java takes a similar approach, but you just “return an array.” Actually, of
course, you’re returning a reference to an array, but with Java you never
worry about responsibility for that array—it will be around as long as you
need it, and the garbage collector will clean it up when you’re done.
2 This is one of the places where C++ is distinctly superior to Java, since C++ supports
parameterized types with the template keyword.
main( ) prints out 20 full sets of flavors, so you can see that flavorSet( )
chooses the flavors in a random order each time. It’s easiest to see this if
you redirect the output into a file. And while you’re looking at the file,
remember, you just want the ice cream, you don’t need it.
While useful, the Arrays class stops short of being fully functional. For
example, it would be nice to be able to easily print the elements of an
array without having to code a for loop by hand every time. And as you’ll
see, the fill( ) method only takes a single value and places it in the array,
so if you wanted—for example—to fill an array with randomly generated
numbers, fill( ) is no help.
Thus it makes sense to supplement the Arrays class with some additional
utilities, which will be placed in the package com.bruceeckel.util for
convenience. These will print an array of any type, and fill an array with
values or objects that are created by an object called a generator that you
can define.
3 The C++ programmer will note how much the code could be collapsed with the use of
default arguments and templates. The Python programmer will note that this entire library
would be largely unnecessary in that language.
//: com:bruceeckel:util:BooleanGenerator.java
package com.bruceeckel.util;
public interface BooleanGenerator {
boolean next();
} ///:~
//: com:bruceeckel:util:ByteGenerator.java
package com.bruceeckel.util;
public interface ByteGenerator {
byte next();
} ///:~
//: com:bruceeckel:util:CharGenerator.java
package com.bruceeckel.util;
public interface CharGenerator {
char next();
} ///:~
//: com:bruceeckel:util:ShortGenerator.java
package com.bruceeckel.util;
public interface ShortGenerator {
short next();
} ///:~
//: com:bruceeckel:util:IntGenerator.java
package com.bruceeckel.util;
public interface IntGenerator {
int next();
} ///:~
//: com:bruceeckel:util:LongGenerator.java
package com.bruceeckel.util;
//: com:bruceeckel:util:FloatGenerator.java
package com.bruceeckel.util;
public interface FloatGenerator {
float next();
} ///:~
//: com:bruceeckel:util:DoubleGenerator.java
package com.bruceeckel.util;
public interface DoubleGenerator {
double next();
} ///:~
Arrays2 contains a variety of print( ) functions, overloaded for each
type. You can simply print an array, you can add a message before the
array is printed, or you can print a range of elements within an array. The
print( ) code is self-explanatory:
//: com:bruceeckel:util:Arrays2.java
// A supplement to java.util.Arrays, to provide
// additional useful functionality when working
// with arrays. Allows any array to be printed,
// and to be filled via a user-defined
// "generator" object.
package com.bruceeckel.util;
import java.util.*;
Random data generators are useful for testing, so a set of inner classes is
created to implement all the primitive generator interfaces, as well as a
String generator to represent Object. You can see that
RandStringGenerator uses RandCharGenerator to fill an array of
characters, which is then turned into a String. The size of the array is
determined by the constructor argument.
Filling an array
The Java standard library Arrays also has a fill( ) method, but that is
rather trivial—it only duplicates a single value into each location, or in the
case of objects, copies the same reference into each location. Using
Arrays2.print( ), the Arrays.fill( ) methods can be easily
demonstrated:
//: c09:FillingArrays.java
// Using Arrays.fill()
import com.bruceeckel.util.*;
import java.util.*;
Copying an array
The Java standard library provides a static method,
System.arraycopy( ), which can make much faster copies of an array
than if you use a for loop to perform the copy by hand.
System.arraycopy( ) is overloaded to handle all types. Here’s an
example that manipulates arrays of int:
//: c09:CopyingArrays.java
// Using System.arraycopy()
import com.bruceeckel.util.*;
import java.util.*;
The example shows that both primitive arrays and object arrays can be
copied. However, if you copy arrays of objects then only the references get
copied—there’s no duplication of the objects themselves. This is called a
shallow copy (see Appendix A).
Comparing arrays
Arrays provides the overloaded method equals( ) to compare entire
arrays for equality. Again, these are overloaded for all the primitives, and
for Object. To be equal, the arrays must have the same number of
elements and each element must be equivalent to each corresponding
element in the other array, using the equals( ) for each element. (For
primitives, that primitive’s wrapper class equals( ) is used; for example,
Integer.equals( ) for int.) Here’s an example:
A problem with writing generic sorting code is that sorting must perform
comparisons based on the actual type of the object. Of course, one
approach is to write a different sorting method for every different type,
but you should be able to recognize that this does not produce code that is
easily reused for new types.
The static randInt( ) method produces positive values between zero and
100, and the generator( ) method produces an object that implements
the Generator interface, by creating an anonymous inner class (see
Chapter 8). This builds CompType objects by initializing them with
random values. In main( ), the generator is used to fill an array of
CompType, which is then sorted. If Comparable hadn’t been
implemented, then you’d get a compile-time error message when you
tried to call sort( ).
The Collections class (which we’ll look at more later) contains a single
Comparator that reverses the natural sorting order. This can easily be
applied to the CompType:
//: c09:Reverse.java
// The Collecions.reverseOrder() Comparator.
import com.bruceeckel.util.*;
import java.util.*;
Sorting an array
With the built-in sorting methods, you can sort any array of primitives,
and any array of objects that either implements Comparable or has an
associated Comparator. This fills a big hole in the Java libraries—
believe it or not, there was no support in Java 1.0 or 1.1 for sorting
Strings! Here’s an example that generates random String objects and
sorts them:
//: c09:StringSorting.java
// Sorting an array of Strings.
import com.bruceeckel.util.*;
import java.util.*;
Array summary
To summarize what you’ve seen so far, your first and most efficient choice
to hold a group of objects should be an array, and you’re forced into this
choice if you want to hold a group of primitives. In the remainder of this
chapter we’ll look at the more general case, when you don’t know at the
time you’re writing the program how many objects you’re going to need,
or if you need a more sophisticated way to store your objects. Java
provides a library of container classes to solve this problem, the basic
types of which are List, Set, and Map. You can solve a surprising
number of problems using these tools.
Introduction to containers
To me, container classes are one of the most powerful tools for raw
development because they significantly increase your programming
muscle. The Java 2 containers represent a thorough redesign4 of the
rather poor showings in Java 1.0 and 1.1. Some of the redesign makes
things tighter and more sensible. It also fills out the functionality of the
The Java 2 container library takes the issue of “holding your objects” and
divides it into two distinct concepts:
Printing containers
Unlike arrays, the containers print nicely without any help. Here’s an
example that also introduces you to the basic types of containers:
//: c09:PrintingContainers.java
// Containers print themselves automatically.
import java.util.*;
The Map holds key-value pairs, rather like a mini database. The above
program uses one flavor of Map, the HashMap. If you have a Map that
associates states with their capitals and you want to know the capital of
Ohio, you look it up—almost as if you were indexing into an array. (Maps
are also called associative arrays.) To add elements to a Map there’s a
put( ) method that takes a key and a value as arguments. The above
example only shows adding elements and does not look the elements up
after they’re added. That will be shown later.
You can also immediately see the basic behavior of the different
containers. The List holds the objects exactly as they are entered, without
any reordering or editing. The Set, however, only accepts one of each
object and it uses its own internal ordering method (in general, you are
only concerned with whether or not something is a member of the Set,
not the order in which it appears—for that you’d use a List). And the
Map also only accepts one of each type of item, based on the key, and it
also has its own internal ordering and does not care about the order in
which you enter the items.
Filling containers
Although the problem of printing the containers is taken care of, filling
containers suffers from the same deficiency as java.util.Arrays. Just
Here is the predefined dataset, which consists of country names and their
capitals. It is set in a small font to prevent taking up unnecessary space:
//: com:bruceeckel:util:CountryCapitals.java
package com.bruceeckel.util;
public class CountryCapitals {
public static final String[][] pairs = {
// Africa
{"ALGERIA","Algiers"}, {"ANGOLA","Luanda"},
{"BENIN","Porto-Novo"}, {"BOTSWANA","Gaberone"},
{"BURKINA FASO","Ouagadougou"}, {"BURUNDI","Bujumbura"},
{"CAMEROON","Yaounde"}, {"CAPE VERDE","Praia"},
{"CENTRAL AFRICAN REPUBLIC","Bangui"},
{"CHAD","N'djamena"}, {"COMOROS","Moroni"},
{"CONGO","Brazzaville"}, {"DJIBOUTI","Dijibouti"},
{"EGYPT","Cairo"}, {"EQUATORIAL GUINEA","Malabo"},
{"ERITREA","Asmara"}, {"ETHIOPIA","Addis Ababa"},
{"GABON","Libreville"}, {"THE GAMBIA","Banjul"},
{"GHANA","Accra"}, {"GUINEA","Conakry"},
{"GUINEA","-"}, {"BISSAU","Bissau"},
{"CETE D'IVOIR (IVORY COAST)","Yamoussoukro"},
{"KENYA","Nairobi"}, {"LESOTHO","Maseru"},
{"LIBERIA","Monrovia"}, {"LIBYA","Tripoli"},
5 This data was found on the Internet, then processed by creating a Python program (see
www.Python.org).
1. Since the type information is thrown away when you put an object
reference into a container, there’s no restriction on the type of
object that can be put into your container, even if you mean it to
hold only, say, cats. Someone could just as easily put a dog into the
container.
2. Since the type information is lost, the only thing the container
knows that it holds is a reference to an object. You must perform a
cast to the correct type before you use it.
On the up side, Java won’t let you misuse the objects that you put into a
container. If you throw a dog into a container of cats and then try to treat
everything in the container as a cat, you’ll get a run-time exception when
you pull the dog reference out of the cat container and try to cast it to a
cat.
//: c09:Dog.java
public class Dog {
private int dogNumber;
Dog(int i) { dogNumber = i; }
void print() {
System.out.println("Dog #" + dogNumber);
}
} ///:~
Cats and Dogs are placed into the container, then pulled out:
//: c09:CatsAndDogs.java
// Simple container example.
import java.util.*;
This is more than just an annoyance. It’s something that can create
difficult-to-find bugs. If one part (or several parts) of a program inserts
objects into a container, and you discover only in a separate part of the
program through an exception that a bad object was placed in the
container, then you must find out where the bad insert occurred. On the
upside, it’s convenient to start with some standardized container classes
for programming, despite the scarcity and awkwardness.
Thus, all you need to do to make objects of your class print is to override
the toString( ) method, as shown in the following example:
//: c09:Mouse.java
// Overriding toString().
public class Mouse {
private int mouseNumber;
Mouse(int i) { mouseNumber = i; }
// Override Object.toString():
//: c09:WorksAnyway.java
// In special cases, things just
// seem to work correctly.
import java.util.*;
class MouseTrap {
static void caughtYa(Object m) {
Mouse mouse = (Mouse)m; // Cast from Object
System.out.println("Mouse: " +
mouse.getNumber());
}
}
Note that if MouseList had instead been inherited from ArrayList, the
add(Mouse) method would simply overload the existing add(Object)
and there would still be no restriction on what type of objects could be
added. Thus, the MouseList becomes a surrogate to the ArrayList,
performing some activities before passing on the responsibility (see
Thinking in Patterns with Java, downloadable at www.BruceEckel.com).
Note that no cast is necessary when using get( )—it’s always a Mouse.
Parameterized types
This kind of problem isn’t isolated—there are numerous cases in which
you need to create new types based on other types, and in which it is
useful to have specific type information at compile-time. This is the
concept of a parameterized type. In C++, this is directly supported by the
language with templates. It is likely that a future version of Java will
support some variation of parameterized types; the current front-runner
automatically creates classes similar to MouseList.
3. See if there are any more objects in the sequence with hasNext( ).
class Hamster {
private int hamsterNumber;
Hamster(int i) { hamsterNumber = i; }
public String toString() {
return "This is Hamster #" + hamsterNumber;
}
}
Although it’s unnecessary, you can be more explicit using a cast, which
has the effect of calling toString( ):
System.out.println((String)e.next());
In general, however, you’ll want to do something more than call Object
methods, so you’ll run up against the type-casting issue again. You must
assume you’ve gotten an Iterator to a sequence of the particular type
you’re interested in, and cast the resulting objects to that type (getting a
run-time exception if you’re wrong).
If you really do want to print the address of the object in this case, the
solution is to call the Object toString( ) method, which does just that.
Container taxonomy
Collections and Maps may be implemented in different ways, according
to your programming needs. It’s helpful to look at a diagram of the Java 2
containers:
Produces Produces
Iterator Collection Map
AbstractMap
Produces
ListIterator List Set
SortedMap
AbstractCollection
SortedSet
HashMap TreeMap
AbstractList AbstractSet
WeakHashMap Hashtable
(Legacy)
HashSet TreeSet
Utilities
Collections
Vector ArrayList AbstractSequentialList
(Legacy)
Arrays
Stack LinkedList
(Legacy)
Comparable Comparator
This diagram can be a bit overwhelming at first, but you’ll see that there
are really only three container components: Map, List, and Set, and only
two or three implementations of each one (with, typically, a preferred
version). When you see this, the containers are not so daunting.
The interfaces that are concerned with holding objects are Collection,
List, Set, and Map. Ideally, you’ll write most of your code to talk to these
interfaces, and the only place where you’ll specify the precise type you’re
using is at the point of creation. So you can create a List like this:
List x = new LinkedList();
Of course, you can also decide to make x a LinkedList (instead of a
generic List) and carry the precise type information around with x. The
beauty (and the intent) of using the interface is that if you decide you
want to change your implementation, all you need to do is change it at the
point of creation, like this:
List x = new ArrayList();
The rest of your code can remain untouched (some of this genericity can
also be achieved with iterators).
In the class hierarchy, you can see a number of classes whose names begin
with “Abstract,” and these can seem a bit confusing at first. They are
simply tools that partially implement a particular interface. If you were
making your own Set, for example, you wouldn’t start with the Set
interface and implement all the methods, instead you’d inherit from
AbstractSet and do the minimal necessary work to make your new class.
However, the containers library contains enough functionality to satisfy
your needs virtually all the time. So for our purposes, you can ignore any
class that begins with “Abstract.”
Therefore, when you look at the diagram, you’re really concerned with
only those interfaces at the top of the diagram and the concrete classes
(those with solid boxes around them). You’ll typically make an object of a
concrete class, upcast it to the corresponding interface, and then use the
ListIterator
HashMap TreeMap
Produces
List Set
WeakHashMap
Utilities
ArrayList LinkedList HashSet TreeSet
Collections
Now it only includes the interfaces and classes that you will encounter on
a regular basis, and also the elements that we will focus on in this chapter.
The add( ) method, as its name suggests, puts a new element in the
Collection. However, the documentation carefully states that add( )
“ensures that this Container contains the specified element.” This is to
allow for the meaning of Set, which adds the element only if it isn’t
already there. With an ArrayList, or any sort of List, add( ) always
means “put it in,” because Lists don’t care if there are duplicates.
Collection functionality
The following table shows everything you can do with a Collection (not
including the methods that automatically come through with Object),
and thus, everything you can do with a Set or a List. (List also has
additional functionality.) Maps are not inherited from Collection, and
will be treated separately.
In addition, there are actually two types of List: the basic ArrayList,
which excels at randomly accessing elements, and the much more
powerful LinkedList (which is not designed for fast random access, but
has a much more general set of methods).
Set functionality
Set has exactly the same interface as Collection, so there isn’t any extra
functionality like there is with the two different Lists. Instead, the Set is
exactly a Collection, it just has different behavior. (This is the ideal use
of inheritance and polymorphism: to express different behavior.) A Set
refuses to hold more than one instance of each object value (what
constitutes the “value” of an object is more complex, as you shall see).
The following example does not show everything you can do with a Set,
since the interface is the same as Collection, and so was exercised in the
previous example. Instead, this demonstrates the behavior that makes a
Set unique:
//: c09:Set1.java
// Things you can do with Sets.
import java.util.*;
import com.bruceeckel.util.*;
When you run this program you’ll notice that the order maintained by the
HashSet is different from TreeSet, since each has a different way of
storing elements so they can be located later. (TreeSet keeps them
sorted, while HashSet uses a hashing function, which is designed
specifically for rapid lookups.) When creating your own types, be aware
that a Set needs a way to maintain a storage order, which means you
must implement the Comparable interface and define the
compareTo( ) method. Here’s an example:
//: c09:Set2.java
// Putting your own type in a Set.
In the compareTo( ), note that I did not use the “simple and obvious”
form return i-i2. Although this is a common programming error, it
would only work properly if i and i2 were “unsigned” ints (if Java had an
“unsigned” keyword, which it does not). It breaks for Java’s signed int,
which is not big enough to represent the difference of two signed ints. If i
is a large positive integer and j is a large negative integer, i-j will overflow
and return a negative value, which will not work.
SortedSet
If you have a SortedSet (of which TreeSet is the only one available), the
elements are guaranteed to be in sorted order which allows additional
functionality to be provided with these methods in the SortedSet
interface:
Comparator comparator(): Produces the Comparator used for
this Set, or null for natural ordering.
Object first(): Produces the lowest element.
Object last(): Produces the highest element.
SortedSet subSet(fromElement, toElement): Produces a view
of this Set with elements from fromElement, inclusive, to
toElement, exclusive.
SortedSet headSet(toElement): Produces a view of this Set with
elements less than toElement.
SortedSet tailSet(fromElement): Produces a view of this Set
with elements greater than or equal to fromElement.
Map functionality
An ArrayList allows you to select from a sequence of objects using a
number, so in a sense it associates numbers to objects. But what if you’d
like to select from a sequence of objects using some other criterion? A
stack is an example: its selection criterion is “the last thing pushed on the
stack.” A powerful twist on this idea of “selecting from a sequence” is
7 If these speedups still don’t meet your performance needs, you can further accelerate
table lookup by writing your own Map and customizing it to your particular types to avoid
delays due to casting to and from Objects. To reach even higher levels of performance,
speed enthusiasts can use Donald Knuth’s The Art of Computer Programming, Volume 3:
Sorting and Searching, Second Edition to replace overflow bucket lists with arrays that
have two additional benefits: they can be optimized for disk storage characteristics and
they can save most of the time of creating and garbage collecting individual records.
Sometimes you’ll also need to know the details of how hashing works, so
we’ll look at that a little later.
The following example uses the Collections2.fill( ) method and the test
data sets that were previously defined:
//: c09:Map1.java
// Things you can do with Maps.
import java.util.*;
import com.bruceeckel.util.*;
The rest of the program provides simple examples of each Map operation,
and tests each type of Map.
class Counter {
int i = 1;
public String toString() {
return Integer.toString(i);
}
}
class Statistics {
public static void main(String[] args) {
HashMap hm = new HashMap();
for(int i = 0; i < 10000; i++) {
// Produce a number between 0 and 20:
Integer r =
new Integer((int)(Math.random() * 20));
if(hm.containsKey(r))
((Counter)hm.get(r)).i++;
else
hm.put(r, new Counter());
}
System.out.println(hm);
If the key has not been found yet, the method put( ) will place a new key-
value pair into the HashMap. Since Counter automatically initializes its
variable i to one when it’s created, it indicates the first occurrence of this
particular random number.
class Groundhog {
int ghNumber;
Groundhog(int n) { ghNumber = n; }
}
class Prediction {
You might think that all you need to do is write an appropriate override
for hashCode( ). But it still won’t work until you’ve done one more
thing: override the equals( ) that is also part of Object. This method is
used by the HashMap when trying to determine if your key is equal to
any of the keys in the table. Again, the default Object.equals( ) simply
compares object addresses, so one Groundhog(3) is not equal to
another Groundhog(3).
Thus, to use your own classes as keys in a HashMap, you must override
both hashCode( ) and equals( ), as shown in the following solution to
the problem above:
//: c09:SpringDetector2.java
// A class that's used as a key in a HashMap
// must override hashCode() and equals().
import java.util.*;
class Groundhog2 {
int ghNumber;
Groundhog2(int n) { ghNumber = n; }
public int hashCode() { return ghNumber; }
public boolean equals(Object o) {
return (o instanceof Groundhog2)
&& (ghNumber == ((Groundhog2)o).ghNumber);
}
}
Even though it appears that the equals( ) method is only checking to see
whether the argument is an instance of Groundhog2 (using the
instanceof keyword, which is fully explained in Chapter 12), the
instanceof actually quietly does a second sanity check, to see if the
object is null, since instanceof produces false if the left-hand argument
is null. Assuming it’s the correct type and not null, the comparison is
based on the actual ghNumbers. This time, when you run the program,
you’ll see it produces the correct output.
When creating your own class to use in a HashSet, you must pay
attention to the same issues as when it is used as a key in a HashMap.
Understanding hashCode( )
The above example is only a start toward solving the problem correctly. It
shows that if you do not override hashCode( ) and equals( ) for your
key, the hashed data structure (HashSet or HashMap) will not be able
to deal with your key properly. However, to get a good solution for the
problem you need to understand what’s going on inside the hashed data
structure.
This shows that it’s not that hard to produce a new type of Map. But as
the name suggests, a SlowMap isn’t very fast, so you probably wouldn’t
use it if you had an alternative available. The problem is in the lookup of
The whole point of hashing is speed: hashing allows the lookup to happen
quickly. Since the bottleneck is in the speed of the key lookup, one of the
solutions to the problem could be by keeping the keys sorted and then
using Collections.binarySearch( ) to perform the lookup (an exercise
at the end of this chapter will walk you through this process).
Hashing goes further by saying that all you want to do is to store the key
somewhere so that it can be quickly found. As you’ve seen in this chapter,
the fastest structure in which to store a group of elements is an array, so
that will be used for representing the key information (note carefully that
I said “key information,” and not the key itself). Also seen in this chapter
was the fact that an array, once allocated, cannot be resized, so we have a
problem: we want to be able to store any number of values in the Map,
but if the number of keys is fixed by the array size, how can this be?
The answer is that the array will not hold the keys. From the key object, a
number will be derived that will index into the array. This number is the
hash code, produced by the hashCode( ) method (in computer science
parlance, this is the hash function) defined in Object and presumably
overridden by your class. To solve the problem of the fixed-size array,
more than one key may produce the same index. That is, there may be
collisions. Because of this, it doesn’t matter how big the array is because
each key object will land somewhere in that array.
The return value of put( ) is null or, if the key was already in the list, the
old value associated with that key. The return value is result, which is
initialized to null, but if a key is discovered in the list then result is
assigned to that key.
For both put( ) and get( ), the first thing that happens is that the
hashCode( ) is called for the key, and the result is forced to a positive
number. Then it is forced to fit into the bucket array using the modulus
In get( ), you’ll see very similar code as that contained in put( ), but
simpler. The index is calculated into the bucket array, and if a
LinkedList exists it is searched for a match.
entrySet( ) must find and traverse all the lists, adding them to the result
Set. Once this method has been created, the Map can be tested by filling
it with values and then printing them.
The default load factor used by HashMap is 0.75 (it doesn’t rehash until
the table is ¾ full). This seems to be a good trade-off between time and
space costs. A higher load factor decreases the space required by the table
but increases the lookup cost, which is important because lookup is what
you do most of the time (including both get( ) and put( )).
Overriding hashCode( )
Now that you understand what’s involved in the function of the
HashMap, the issues involved in writing a hashCode( ) will make more
sense.
First of all, you don’t have control of the creation of the actual value that’s
used to index into the array of buckets. That is dependent on the capacity
of the particular HashMap object, and that capacity changes depending
on how full the container is, and what the load factor is. The value
produced by your hashCode( ) will be further processed in order to
create the bucket index (in SimpleHashMap the calculation is just a
modulo by the size of the bucket array).
One example is found in the String class. Strings have the special
characteristic that if a program has several String objects that contain
identical character sequences, then those String objects all map to the
same memory (the mechanism for this is described in Appendix A). So it
Holding references
The java.lang.ref library contains a set of classes that allow greater
flexibility in garbage collection, which are especially useful when you have
large objects that may cause memory exhaustion. There are three classes
inherited from the abstract class Reference: SoftReference,
WeakReference, and PhantomReference. Each of these provides a
different level of indirection for the garbage collector, if the object in
question is only reachable through one of these Reference objects.
You use Reference objects when you want to continue to hold onto a
reference to that object—you want to be able to reach that object—but you
also want to allow the garbage collector to release that object. Thus, you
have a way to go on using the object, but if memory exhaustion is
imminent you allow that object to be released.
class VeryBig {
static final int SZ = 10000;
double[] d = new double[SZ];
String ident;
public VeryBig(String id) { ident = id; }
public String toString() { return ident; }
public void finalize() {
System.out.println("Finalizing " + ident);
}
}
The WeakHashMap
The containers library has a special Map to hold weak references: the
WeakHashMap. This class is designed to make the creation of
canonicalized mappings easier. In such a mapping, you are saving storage
by making only one instance of a particular value. When the program
needs that value, it looks up the existing object in the mapping and uses
that (rather than creating one from scratch). The mapping may make the
values as part of its initialization, but it’s more likely that the values are
made on demand.
class Key {
String ident;
public Key(String id) { ident = id; }
public String toString() { return ident; }
public int hashCode() {
return ident.hashCode();
}
class Value {
String ident;
public Value(String id) { ident = id; }
public String toString() { return ident; }
public void finalize() {
System.out.println("Finalizing Value "+ident);
}
}
Iterators revisited
We can now demonstrate the true power of the Iterator: the ability to
separate the operation of traversing a sequence from the underlying
structure of that sequence. In the following example, the class PrintData
uses an Iterator to move through a sequence and call the toString( )
method for every object. Two different types of containers are created—an
ArrayList and a HashMap—and they are each filled with, respectively,
Mouse and Hamster objects. (These classes are defined earlier in this
chapter.) Because an Iterator hides the structure of the underlying
container, PrintData doesn’t know or care what kind of container the
Iterator comes from:
//: c09:Iterators2.java
// Revisiting Iterators.
import java.util.*;
class PrintData {
static void print(Iterator e) {
while(e.hasNext())
System.out.println(e.next());
}
}
class Iterators2 {
public static void main(String[] args) {
ArrayList v = new ArrayList();
for(int i = 0; i < 5; i++)
v.add(new Mouse(i));
HashMap m = new HashMap();
for(int i = 0; i < 5; i++)
m.put(new Integer(i), new Hamster(i));
System.out.println("ArrayList");
PrintData.print(v.iterator());
System.out.println("HashMap");
Note that PrintData.print( ) takes advantage of the fact that the objects
in these containers are of class Object so the call toString( ) by
System.out.println( ) is automatic. It’s more likely that in your
problem, you must make the assumption that your Iterator is walking
through a container of some specific type. For example, you might assume
that everything in the container is a Shape with a draw( ) method. Then
you must downcast from the Object that Iterator.next( ) returns to
produce a Shape.
Choosing an
implementation
By now you should understand that there are really only three container
components: Map, List, and Set, and only two or three implementations
of each interface. If you need to use the functionality offered by a
particular interface, how do you decide which particular implementation
to use?
The distinction between the other containers often comes down to what
they are “backed by”; that is, the data structures that physically
implement your desired interface. This means that, for example,
ArrayList and LinkedList implement the List interface so your
program will produce the same results regardless of the one you use.
The List that’s handed to test( ) is first filled with elements, then each
test in the tests array is timed. The results will vary from machine to
machine; they are intended to give only an order of magnitude
comparison between the performance of the different containers. Here is
a summary of one run:
Utilities
There are a number of other useful utilities in the Collections class:
Note that min( ) and max( ) work with Collection objects, not with
Lists, so you don’t need to worry about whether the Collection should
be sorted or not. (As mentioned earlier, you do need to sort( ) a List or
an array before performing a binarySearch( ).)
Calling the “unmodifiable” method for a particular type does not cause
compile-time checking, but once the transformation has occurred, any
calls to methods that modify the contents of a particular container will
produce an UnsupportedOperationException.
Fail fast
The Java containers also have a mechanism to prevent more than one
process from modifying the contents of a container. The problem occurs if
you’re iterating through a container and some other process steps in and
inserts, removes, or changes an object in that container. Maybe you’ve
already passed that object, maybe it’s ahead of you, maybe the size of the
container shrinks after you call size( )—there are many scenarios for
disaster. The Java containers library incorporates a fail-fast mechanism
that looks for any changes to the container other than the ones your
process is personally responsible for. If it detects that someone else is
modifying the container, it immediately produces a
ConcurrentModificationException. This is the “fail-fast” aspect—it
doesn’t try to detect a problem later on using a more complex algorithm.
Note that you cannot benefit from this kind of monitoring when you’re
accessing the elements of a List using get( ).
Unsupported operations
It’s possible to turn an array into a List with the Arrays.asList( )
method:
//: c09:Unsupported.java
// Sometimes methods defined in the
// Collection interfaces don't work!
import java.util.*;
“What?!?” you say, incredulous. “The whole point of interfaces and base
classes is that they promise these methods will do something meaningful!
This breaks that promise—it says that not only will calling some methods
not perform a meaningful behavior, they will stop the program! Type
safety was just thrown out the window!”
It’s not quite that bad. With a Collection, List, Set, or Map, the
compiler still restricts you to calling only the methods in that interface,
so it’s not like Smalltalk (in which you can call any method for any object,
and find out only when you run the program whether your call does
anything). In addition, most methods that take a Collection as an
argument only read from that Collection—all the “read” methods of
Collection are not optional.
The Java 1.0/1.1 version of the iterator chose to invent a new name,
“enumeration,” instead of using a term that everyone was already familiar
with. The Enumeration interface is smaller than Iterator, with only
two methods, and it uses longer method names: boolean
hasMoreElements( ) produces true if this enumeration contains more
elements, and Object nextElement( ) returns the next element of this
enumeration if there are any more (otherwise it throws an exception).
class Enumerations {
public static void main(String[] args) {
Vector v = new Vector();
Collections2.fill(
v, Collections2.countries, 100);
Enumeration e = v.elements();
while(e.hasMoreElements())
System.out.println(e.nextElement());
Hashtable
As you’ve seen in the performance comparison in this chapter, the basic
Hashtable is very similar to the HashMap, even down to the method
names. There’s no reason to use Hashtable instead of HashMap in new
code.
Stack
The concept of the stack was introduced earlier, with the LinkedList.
What’s rather odd about the Java 1.0/1.1 Stack is that instead of using a
Vector as a building block, Stack is inherited from Vector. So it has all
of the characteristics and behaviors of a Vector plus some extra Stack
behaviors. It’s difficult to know whether the designers explicitly decided
that this was an especially useful way of doing things, or whether it was
just a naive design.
As mentioned earlier, you should use a LinkedList when you want stack
behavior.
BitSet
A BitSet is used if you want to efficiently store a lot of on-off information.
It’s efficient only from the standpoint of size; if you’re looking for efficient
access, it is slightly slower than using an array of some native type.
A normal container expands as you add more elements, and the BitSet
does this as well. The following example shows how the BitSet works:
//: c09:Bits.java
short st = (short)rand.nextInt();
BitSet bs = new BitSet();
for(int i = 15; i >=0; i--)
if(((1 << i) & st) != 0)
bs.set(i);
else
bs.clear(i);
System.out.println("short value: " + st);
printBitSet(bs);
int it = rand.nextInt();
BitSet bi = new BitSet();
for(int i = 31; i >=0; i--)
if(((1 << i) & it) != 0)
bi.set(i);
else
bi.clear(i);
Summary
To review the containers provided in the standard Java library:
The containers are tools that you can use on a day-to-day basis to make
your programs simpler, more powerful, and more effective.
Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in Java
Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
4. Take the Gerbil class in Exercise 2 and put it into a Map instead,
associating the name of the Gerbil as a String (the key) for each
Gerbil (the value) you put in the table. Get an Iterator for the
keySet( ) and use it to move through the Map, looking up the
Gerbil for each key and printing out the key and telling the
gerbil to hop( ).
16. Create both an ArrayList and a LinkedList, and fill each using
the Collections2.capitals generator. Print each list using an
ordinary Iterator, then insert one list into the other using a
ListIterator, inserting at every other location. Now perform the
insertion starting at the end of the first list and moving backward.
22. Use a TreeMap in Statistics.java. Now add code that tests the
performance difference between HashMap and TreeMap in that
program.
23. Produce a Map and a Set containing all the countries that begin
with ‘A.’
25. Starting with Statistics.java, create a program that runs the test
repeatedly and looks to see if any one number tends to appear
more than the others in the results.
27. Modify the class in Exercise 13 so that it will work with HashSets
and as a key in HashMaps.
41. Using the HTML documentation for the JDK (downloadable from
java.sun.com), look up the HashMap class. Create a HashMap,
fill it with elements, and determine the load factor. Test the lookup
speed with this map, then attempt to increase the speed by making
a new HashMap with a larger initial capacity and copying the old
43. (Challenging). Write your own hashed map class, customized for a
particular key type: String for this example. Do not inherit it from
Map. Instead, duplicate the methods so that the put( ) and get( )
methods specifically take String objects, not Objects, as keys.
Everything that involves keys should not use generic types, but
instead work with Strings, to avoid the cost of upcasting and
downcasting. Your goal is to make the fastest possible custom
implementation. Modify MapPerformance.java to test your
implementation vs. a HashMap.
44. (Challenging). Find the source code for List in the Java source
code library that comes with all Java distributions. Copy this code
and make a special version called intList that holds only ints.
Consider what it would take to make a special version of List for
all the primitive types. Now consider what happens if you want to
make a linked list class that works with all the primitive types. If
parameterized types are ever implemented in Java, they will
provide a way to do this work for you automatically (as well as
many other benefits).
The solution is to take the casual nature out of error handling and to
enforce formality. This actually has a long history, since implementations
of exception handling go back to operating systems in the 1960s, and even
1 The C programmer can look up the return value of printf( ) for an example of this.
531
to BASIC’s “on error goto.” But C++ exception handling was based on
Ada, and Java’s is based primarily on C++ (although it looks even more
like Object Pascal).
Basic exceptions
An exceptional condition is a problem that prevents the continuation of
the method or scope that you’re in. It’s important to distinguish an
exceptional condition from a normal problem, in which you have enough
information in the current context to somehow cope with the difficulty.
With an exceptional condition, you cannot continue processing because
you don’t have the information necessary to deal with the problem in the
current context. All you can do is jump out of the current context and
When you throw an exception, several things happen. First, the exception
object is created in the same way that any Java object is created: on the
heap, with new. Then the current path of execution (the one you couldn’t
continue) is stopped and the reference for the exception object is ejected
from the current context. At this point the exception handling mechanism
takes over and begins to look for an appropriate place to continue
executing the program. This appropriate place is the exception handler,
whose job is to recover from the problem so the program can either try
another tack or just continue.
Exception arguments
Like any object in Java, you always create exceptions on the heap using
new, which allocates storage and calls a constructor. There are two
In addition, you can throw any type of Throwable object that you want.
Typically, you’ll throw a different class of exception for each different type
of error. The information about the error is represented both inside the
exception object and implicitly in the type of exception object chosen, so
someone in the bigger context can figure out what to do with your
exception. (Often, the only information is the type of exception object, and
nothing meaningful is stored within the exception object.)
Catching an exception
If a method throws an exception, it must assume that exception is
“caught” and dealt with. One of the advantages of Java exception handling
is that it allows you to concentrate on the problem you’re trying to solve in
one place, and then deal with the errors from that code in another place.
Exception handlers
Of course, the thrown exception must end up someplace. This “place” is
the exception handler, and there’s one for every exception type you want
to catch. Exception handlers immediately follow the try block and are
denoted by the keyword catch:
try {
// Code that might generate exceptions
} catch(Type1 id1) {
// Handle exceptions of Type1
} catch(Type2 id2) {
// Handle exceptions of Type2
} catch(Type3 id3) {
// Handle exceptions of Type3
// etc...
Each catch clause (exception handler) is like a little method that takes one
and only one argument of a particular type. The identifier (id1, id2, and
so on) can be used inside the handler, just like a method argument.
Sometimes you never use the identifier because the type of the exception
gives you enough information to deal with the exception, but the identifier
must still be there.
The handlers must appear directly after the try block. If an exception is
thrown, the exception handling mechanism goes hunting for the first
handler with an argument that matches the type of the exception. Then it
enters that catch clause, and the exception is considered handled. The
search for handlers stops once the catch clause is finished. Only the
matching catch clause executes; it’s not like a switch statement in which
you need a break after each case to prevent the remaining ones from
executing.
Note that, within the try block, a number of different method calls might
generate the same exception, but you need only one handler.
Here, the result is printed to the console standard error stream by writing
to System.err. This is usually a better place to send error information
than System.out, which may be redirected. If you send output to
System.err it will not be redirected along with System.out so the user
is more likely to notice it.
Creating an exception class that also has a constructor that takes a String
is also quite simple:
//: c10:FullConstructors.java
// Inheriting your own exceptions.
The stack trace information is sent to System.err so that it’s more likely
it will be noticed in the event that System.out has been redirected.
You can see the absence of the detail message in the MyException
thrown from f( ).
2 This is a significant improvement over C++ exception handling, which doesn’t catch
violations of exception specifications until run time, when it’s not very useful.
Since the Exception class is the base of all the exception classes that are
important to the programmer, you don’t get much specific information
about the exception, but you can call the methods that come from its base
type Throwable:
String getMessage( )
String getLocalizedMessage( )
Gets the detail message, or a message adjusted for this particular locale.
String toString( )
Returns a short description of the Throwable, including the detail
message if there is one.
void printStackTrace( )
void printStackTrace(PrintStream)
void printStackTrace(PrintWriter)
Prints the Throwable and the Throwable’s call stack trace. The call stack
Throwable fillInStackTrace( )
Records information within this Throwable object about the current
state of the stack frames. Useful when an application is rethrowing an
error or exception (more about this shortly).
In addition, you get some other methods from Throwable’s base type
Object (everybody’s base type). The one that might come in handy for
exceptions is getClass( ), which returns an object representing the class
of this object. You can in turn query this Class object for its name with
getName( ) or toString( ). You can also do more sophisticated things
with Class objects that aren’t necessary in exception handling. Class
objects will be studied later in this book.
Here’s an example that shows the use of the basic Exception methods:
//: c10:ExceptionMethods.java
// Demonstrating the Exception Methods.
Rethrowing an exception
Sometimes you’ll want to rethrow the exception that you just caught,
particularly when you use Exception to catch any exception. Since you
already have the reference to the current exception, you can simply
rethrow that reference:
catch(Exception e) {
System.err.println("An exception was thrown");
throw e;
}
Rethrowing an exception causes the exception to go to the exception
handlers in the next-higher context. Any further catch clauses for the
same try block are still ignored. In addition, everything about the
exception object is preserved, so the handler at the higher context that
catches the specific exception type can extract all the information from
that object.
If you simply rethrow the current exception, the information that you
print about that exception in printStackTrace( ) will pertain to the
exception’s origin, not the place where you rethrow it. If you want to
install new stack trace information, you can do so by calling
fillInStackTrace( ), which returns an exception object that it creates by
You never have to worry about cleaning up the previous exception, or any
exceptions for that matter. They’re all heap-based objects created with
new, so the garbage collector automatically cleans them all up.
The best way to get an overview of the exceptions is to browse the HTML
Java documentation that you can download from java.sun.com. It’s worth
doing this once just to get a feel for the various exceptions, but you’ll soon
see that there isn’t anything special between one exception and the next
except for the name. Also, the number of exceptions in Java keeps
expanding; basically it’s pointless to print them in a book. Any new library
you get from a third-party vendor will probably have its own exceptions as
well. The important thing to understand is the concept and what you
should do with the exceptions.
There’s a whole group of exception types that are in this category. They’re
always thrown automatically by Java and you don’t need to include them
in your exception specifications. Conveniently enough, they’re all grouped
together by putting them under a single base class called
RuntimeException, which is a perfect example of inheritance: it
establishes a family of types that have some characteristics and behaviors
in common. Also, you never need to write an exception specification
saying that a method might throw a RuntimeException, since that’s
just assumed. Because they indicate bugs, you virtually never catch a
RuntimeException—it’s dealt with automatically. If you were forced to
check for RuntimeExceptions your code could get messy. Even though
you don’t typically catch RuntimeExceptions, in your own packages
you might choose to throw some of the RuntimeExceptions.
What happens when you don’t catch such exceptions? Since the compiler
doesn’t enforce exception specifications for these, it’s quite plausible that
a RuntimeException could percolate all the way out to your main( )
It’s interesting to notice that you cannot classify Java exception handling
as a single-purpose tool. Yes, it is designed to handle those pesky run-time
errors that will occur because of forces outside your code’s control, but it’s
also essential for certain types of programming bugs that the compiler
cannot detect.
Performing cleanup
with finally
There’s often some piece of code that you want to execute whether or not
an exception is thrown within a try block. This usually pertains to some
operation other than memory recovery (since that’s taken care of by the
garbage collector). To achieve this effect, you use a finally clause3 at the
end of all the exception handlers. The full picture of an exception
handling section is thus:
try {
// The guarded region: Dangerous activities
// that might throw A, B, or C
} catch(A a1) {
// Handler for situation A
} catch(B b1) {
// Handler for situation B
} catch(C c1) {
// Handler for situation C
} finally {
// Activities that happen every time
3 C++ exception handling does not have the finally clause because it relies on destructors
to accomplish this sort of cleanup.
finally is necessary when you need to set something other than memory
back to its original state. This is some kind of cleanup like an open file or
network connection, something you’ve drawn on the screen, or even a
switch in the outside world, as modeled in the following example:
//: c10:OnOffSwitch.java
// Why use finally?
class Switch {
boolean state = false;
boolean read() { return state; }
void on() { state = true; }
void off() { state = false; }
}
class OnOffException1 extends Exception {}
class OnOffException2 extends Exception {}
4 A destructor is a function that’s always called when an object becomes unused. You
always know exactly where and when the destructor gets called. C++ has automatic
destructor calls, but Delphi’s Object Pascal versions 1 and 2 do not (which changes the
meaning and use of the concept of a destructor for that language).
Even in cases in which the exception is not caught in the current set of
catch clauses, finally will be executed before the exception handling
mechanism continues its search for a handler at the next higher level:
//: c10:AlwaysFinally.java
// Finally is always executed.
Exception restrictions
When you override a method, you can throw only the exceptions that have
been specified in the base-class version of the method. This is a useful
restriction, since it means that code that works with the base class will
automatically work with any object derived from the base class (a
fundamental OOP concept, of course), including exceptions.
interface Storm {
void event() throws RainedOut;
void rainHard() throws RainedOut;
}
The restriction on exceptions does not apply to constructors. You can see
in StormyInning that a constructor can throw anything it wants,
regardless of what the base-class constructor throws. However, since a
base-class constructor must always be called one way or another (here,
the default constructor is called automatically), the derived-class
constructor must declare any base-class constructor exceptions in its
exception specification. Note that a derived-class constructor cannot catch
exceptions thrown by its base-class constructor.
The last point of interest is in main( ). Here you can see that if you’re
dealing with exactly a StormyInning object, the compiler forces you to
Constructors
When writing code with exceptions, it’s particularly important that you
always ask, “If an exception occurs, will this be properly cleaned up?”
Most of the time you’re fairly safe, but in constructors there’s a problem.
The constructor puts the object into a safe starting state, but it might
perform some operation—such as opening a file—that doesn’t get cleaned
up until the user is finished with the object and calls a special cleanup
method. If you throw an exception from inside a constructor, these
cleanup behaviors might not occur properly. This means that you must be
especially diligent while you write your constructor.
Since you’ve just learned about finally, you might think that it is the
correct solution. But it’s not quite that simple, because finally performs
the cleanup code every time, even in the situations in which you don’t
want the cleanup code executed until the cleanup method runs. Thus, if
5 ISO C++ added similar constraints that require derived-method exceptions to be the
same as, or derived from, the exceptions thrown by the base-class method. This is one case
in which C++ is actually able to check exception specifications at compile-time.
class InputFile {
private BufferedReader in;
InputFile(String fname) throws Exception {
try {
in =
new BufferedReader(
new FileReader(fname));
// Other code that might throw exceptions
} catch(FileNotFoundException e) {
System.err.println(
"Could not open " + fname);
// Wasn't open, so don't close it
throw e;
} catch(Exception e) {
// All other exceptions must close it
try {
in.close();
} catch(IOException e2) {
System.err.println(
"in.close() unsuccessful");
}
throw e; // Rethrow
} finally {
The getLine( ) method returns a String containing the next line in the
file. It calls readLine( ), which can throw an exception, but that
exception is caught so getLine( ) doesn’t throw any exceptions. One of
the design issues with exceptions is whether to handle an exception
completely at this level, to handle it partially and pass the same exception
(or a different one) on, or whether to simply pass it on. Passing it on,
when appropriate, can certainly simplify coding. The getLine( ) method
becomes:
String getLine() throws IOException {
return in.readLine();
}
The cleanup( ) method must be called by the user when finished using
the InputFile object. This will release the system resources (such as file
handles) that are used by the BufferedReader and/or FileReader
objects6. You don’t want to do this until you’re finished with the
InputFile object, at the point you’re going to let it go. You might think of
putting such functionality into a finalize( ) method, but as mentioned in
Chapter 4 you can’t always be sure that finalize( ) will be called (even if
you can be sure that it will be called, you don’t know when). This is one of
the downsides to Java: all cleanup—other than memory cleanup—doesn’t
happen automatically, so you must inform the client programmer that
they are responsible, and possibly guarantee that cleanup occurs using
finalize( ).
One of the benefits of this example is to show you why exceptions are
introduced at this point in the book—you can’t do basic I/O without using
exceptions. Exceptions are so integral to programming in Java, especially
because the compiler enforces them, that you can accomplish only so
much without knowing how to work with them.
Exception matching
When an exception is thrown, the exception handling system looks
through the “nearest” handlers in the order they are written. When it
finds a match, the exception is considered handled, and no further
searching occurs.
Exception guidelines
Use exceptions to:
1. Fix the problem and call the method that caused the exception
again.
4. Do whatever you can in the current context and rethrow the same
exception to a higher context.
Summary
Improved error recovery is one of the most powerful ways that you can
increase the robustness of your code. Error recovery is a fundamental
concern for every program you write, but it’s especially important in Java,
The goals for exception handling in Java are to simplify the creation of
large, reliable programs using less code than currently possible, and with
more confidence that your application doesn’t have an unhandled error.
Exceptions are not terribly difficult to learn, and are one of those features
that provide immediate and significant benefits to your project.
Fortunately, Java enforces all aspects of exceptions so it’s guaranteed that
they will be used consistently by both library designers and client
programmers.
Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in Java
Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
14. Create an example where you use a flag to control whether cleanup
code is called, as described in the second paragraph after the
heading “Constructors.”
This chapter will give you an introduction to the variety of I/O classes in
the standard Java library and how to use them.
573
The File class
Before getting into the classes that actually read and write data to
streams, we’ll look a utility provided with the library to assist you in
handling file directory issues.
The File class has a deceiving name—you might think it refers to a file,
but it doesn’t. It can represent either the name of a particular file or the
names of a set of files in a directory. If it’s a set of files, you can ask for the
set with the list( ) method, and this returns an array of String. It makes
sense to return an array rather than one of the flexible container classes
because the number of elements is fixed, and if you want a different
directory listing you just create a different File object. In fact, “FilePath”
would have been a better name for the class. This section shows an
example of the use of this class, including the associated FilenameFilter
interface.
A directory lister
Suppose you’d like to see a directory listing. The File object can be listed
in two ways. If you call list( ) with no arguments, you’ll get the full list
that the File object contains. However, if you want a restricted list—for
example, if you want all of the files with an extension of .java—then you
use a “directory filter,” which is a class that tells how to select the File
objects for display.
Here’s the code for the example. Note that the result has been effortlessly
sorted (alphabetically) using the java.utils.Array.sort( ) method and
the AlphabeticComparator defined in Chapter 9:
//: c11:DirList.java
// Displays directory listing.
import java.io.*;
import java.util.*;
import com.bruceeckel.util.*;
To make sure the element you’re working with is only the file name and
contains no path information, all you have to do is take the String object
and create a File object out of it, then call getName( ), which strips away
all the path information (in a platform-independent way). Then accept( )
uses the String class indexOf( ) method to see if the search string afn
appears anywhere in the name of the file. If afn is found within the string,
the return value is the starting index of afn, but if it’s not found the return
value is -1. Keep in mind that this is a simple string search and does not
have “glob” expression wildcard matching—such as “fo?.b?r*”—which is
much more difficult to implement.
The list( ) method returns an array. You can query this array for its
length and then move through it selecting the array elements. This ability
to easily pass an array in and out of a method is a tremendous
improvement over the behavior of C and C++.
This shows you how anonymous inner classes allow the creation of quick-
and-dirty classes to solve problems. Since everything in Java revolves
around classes, this can be a useful coding technique. One benefit is that it
keeps the code that solves a particular problem isolated together in one
spot. On the other hand, it is not always as easy to read, so you must use it
judiciously.
If you experiment with the above program, you’ll find that you can make a
directory path of any complexity because mkdirs( ) will do all the work
for you.
The Java library classes for I/O are divided by input and output, as you
can see by looking at the online Java class hierarchy with your Web
browser. By inheritance, everything derived from the InputStream or
Reader classes have basic methods called read( ) for reading a single
byte or array of bytes. Likewise, everything derived from OutputStream
or Writer classes have basic methods called write( ) for writing a single
byte or array of bytes. However, you won’t generally use these methods;
they exist so that other classes can use them—these other classes provide
a more useful interface. Thus, you’ll rarely create your stream object by
using a single class, but instead will layer multiple objects together to
provide your desired functionality. The fact that you create more than one
object to create a single resulting stream is the primary reason that Java’s
stream library is confusing.
It’s helpful to categorize the classes by their functionality. In Java 1.0, the
library designers started by deciding that all classes that had anything to
do with input would be inherited from InputStream and all classes that
were associated with output would be inherited from OutputStream.
Types of InputStream
InputStream’s job is to represent classes that produce input from
different sources. These sources can be:
1. An array of bytes.
2. A String object.
3. A file.
4. A “pipe,” which works like a physical pipe: you put things in one
end and they come out the other.
Types of OutputStream
This category includes the classes that decide where your output will go:
an array of bytes (no String, however; presumably you can create one
using the array of bytes), a file, or a “pipe.”
You’ll probably need to buffer your input almost every time, regardless of
the I/O device you’re connecting to, so it would have made more sense for
the I/O library to make a special case (or simply a method call) for
unbuffered input rather than buffered input.
Writing to an OutputStream
with FilterOutputStream
The complement to DataInputStream is DataOutputStream, which
formats each of the primitive types and String objects onto a stream in
such a way that any DataInputStream, on any machine, can read them.
All the methods start with “write,” such as writeByte( ), writeFloat( ),
etc.
The original intent of PrintStream was to print all of the primitive data
types and String objects in a viewable format. This is different from
DataOutputStream, whose goal is to put data elements on a stream in a
way that DataInputStream can portably reconstruct them.
2. There are times when you must use classes from the “byte”
hierarchy in combination with classes in the “character” hierarchy.
To accomplish this there are “bridge” classes:
InputStreamReader converts an InputStream to a Reader
and OutputStreamWriter converts an OutputStream to a
Writer.
Here is a table that shows the correspondence between the sources and
sinks of information (that is, where the data physically comes from or
goes to) in the two hierarchies.
In general, you’ll find that the interfaces for the two different hierarchies
are similar if not identical.
There’s one direction that’s quite clear: Whenever you want to use
readLine( ), you shouldn’t do it with a DataInputStream any more
(this is met with a deprecation message at compile-time), but instead use
a BufferedReader. Other than this, DataInputStream is still a
“preferred” member of the I/O library.
Unchanged Classes
Some classes were left unchanged between Java 1.0 and Java 1.1:
At first it’s a little bit hard to believe that RandomAccessFile is not part
of the InputStream or OutputStream hierarchy. However, it has no
association with those hierarchies other than that it happens to
implement the DataInput and DataOutput interfaces (which are also
implemented by DataInputStream and DataOutputStream). It
doesn’t even use any of the functionality of the existing InputStream or
OutputStream classes—it’s a completely separate class, written from
scratch, with all of its own (mostly native) methods. The reason for this
may be that RandomAccessFile has essentially different behavior than
the other I/O types, since you can move forward and backward within a
file. In any event, it stands alone, as a direct descendant of Object.
// 4. File output
try {
BufferedReader in4 =
new BufferedReader(
new StringReader(s2));
PrintWriter out1 =
new PrintWriter(
new BufferedWriter(
new FileWriter("IODemo.out")));
int lineCount = 1;
while((s = in4.readLine()) != null )
out1.println(lineCount++ + ": " + s);
out1.close();
} catch(EOFException e) {
System.err.println("End of stream");
}
rf =
new RandomAccessFile("rtest.dat", "rw");
rf.seek(5*8);
rf.writeDouble(47.0001);
rf.close();
rf =
new RandomAccessFile("rtest.dat", "r");
for(int i = 0; i < 10; i++)
System.out.println(
"Value " + i + ": " +
rf.readDouble());
rf.close();
}
} ///:~
Here are the descriptions for the numbered sections of the program:
Section 1b shows how you can wrap System.in for reading console input.
System.in is a DataInputStream and BufferedReader needs a
Reader argument, so InputStreamReader is brought in to perform
the translation.
You could also detect the end of input in cases like these by catching an
exception. However, the use of exceptions for control flow is considered a
misuse of that feature.
As the lines are written to the file, line numbers are added. Note that
LineNumberInputStream is not used, because it’s a silly class and you
don’t need it. As shown here, it’s trivial to keep track of your own line
numbers.
When the input stream is exhausted, readLine( ) returns null. You’ll see
an explicit close( ) for out1, because if you don’t call close( ) for all your
output files, you might discover that the buffers don’t get flushed so
they’re incomplete.
Output streams
The two primary kinds of output streams are separated by the way they
write data: one writes it for human consumption, and the other writes it
to be reacquired by a DataInputStream. The RandomAccessFile
stands alone, although its data format is compatible with the
DataInputStream and DataOutputStream.
Note that the character string is written using both writeChars( ) and
writeBytes( ). When you run the program, you’ll discover that
writeChars( ) outputs 16-bit Unicode characters. When you read the
line using readLine( ), you’ll see that there is a space between each
character, because of the extra byte inserted by Unicode. Since there is no
complementary “readChars” method in DataInputStream, you’re stuck
pulling these characters off one at a time with readChar( ). So for ASCII,
it’s easier to write the characters as bytes followed by a newline; then use
readLine( ) to read back the bytes as a regular ASCII line.
The writeDouble( ) stores the double number to the stream and the
complementary readDouble( ) recovers it (there are similar methods for
reading and writing the other types). But for any of the reading methods
to work correctly, you must know the exact placement of the data item in
the stream, since it would be equally possible to read the stored double
as a simple sequence of bytes, or as a char, etc. So you must either have a
fixed format for the data in the file or extra information must be stored in
the file that you parse to determine where the data is located.
2 XML is another way to solve the problem of moving data across different computing
platforms, and does not depend on having Java on all platforms. However, Java tools exist
that support XML.
A bug?
If you look at section 5, you’ll see that the data is written before the text.
That’s because a problem was introduced in Java 1.1 (and persists in Java
2) that sure seems like a bug to me, but I reported it and the bug people at
JavaSoft said that this is the way it is supposed to work (however, the
problem did not occur in Java 1.0, which makes me suspicious). The
problem is shown in the following code:
//: c11:IOProblem.java
// Java 1.1 and higher I/O Problem.
import java.io.*;
DataInputStream in =
new DataInputStream(
new BufferedInputStream(
new FileInputStream("Data.txt")));
BufferedReader inbr =
new BufferedReader(
Piped streams
The PipedInputStream, PipedOutputStream, PipedReader and
PipedWriter have been mentioned only briefly in this chapter. This is
not to suggest that they aren’t useful, but their value is not apparent until
you begin to understand multithreading, since the piped streams are used
to communicate between threads. This is covered along with an example
in Chapter 14.
Standard I/O
The term standard I/O refers to the Unix concept (which is reproduced in
some form in Windows and many other operating systems) of a single
stream of information that is used by a program. All the program’s input
can come from standard input, all its output can go to standard output,
and all of its error messages can be sent to standard error. The value of
standard I/O is that programs can easily be chained together and one
program’s standard output can become the standard input for another
program. This is a powerful tool.
setIn(InputStream)
setOut(PrintStream)
setErr(PrintStream)
3 Chapter 13 shows an even more convenient solution for this: a GUI program with a
scrolling text area.
class Redirecting {
// Throw exceptions to console:
public static void main(String[] args)
throws IOException {
BufferedInputStream in =
new BufferedInputStream(
new FileInputStream(
"Redirecting.java"));
PrintStream out =
new PrintStream(
new BufferedOutputStream(
new FileOutputStream("test.out")));
System.setIn(in);
System.setOut(out);
System.setErr(out);
BufferedReader br =
new BufferedReader(
new InputStreamReader(System.in));
String s;
while((s = br.readLine()) != null)
System.out.println(s);
out.close(); // Remember this!
}
} ///:~
This program attaches standard input to a file, and redirects standard
output and standard error to another file.
These classes are not derived from the Reader and Writer classes, but
instead are part of the InputStream and OutputStream hierarchies.
This is because the compression library works with bytes, not characters.
However, you might sometimes be forced to mix the two types of streams.
(Remember that you can use InputStreamReader and
OutputStreamWriter to provide easy conversion between one type and
another.)
Although there are many compression algorithms, Zip and GZIP are
possibly the most commonly used. Thus you can easily manipulate your
In order to read the checksum you must somehow have access to the
associated Checksum object. Here, a reference to the
CheckedOutputStream and CheckedInputStream objects is
retained, but you could also just hold onto a reference to the Checksum
object.
Of course, you are not limited to files when using the GZIP or Zip
libraries—you can compress anything, including data to be sent through a
network connection.
JAR files are particularly helpful when you deal with the Internet. Before
JAR files, your Web browser would have to make repeated requests of a
Web server in order to download all of the files that make up an applet. In
addition, each of these files was uncompressed. By combining all of the
files for a particular applet into a single JAR file, only one server request
is necessary and the transfer is faster because of compression. And each
entry in a JAR file can be digitally signed for security (refer to the Java
documentation for details).
The jar utility that comes with Sun’s JDK automatically compresses the
files of your choice. You invoke it on the command line:
jar [options] destination [manifest] inputfile(s)
The options are simply a collection of letters (no hyphen or any other
indicator is necessary). Unix/Linux users will note the similarity to the
tar options. These are:
If a subdirectory is included in the files to be put into the JAR file, that
subdirectory is automatically added, including all of its subdirectories,
etc. Path information is also preserved.
Adds the “verbose” flag to give more detailed information about the files
in myJarFile.jar.
jar cvf myApp.jar audio classes image
If you create a JAR file using the 0 option, that file can be placed in your
CLASSPATH:
CLASSPATH="lib1.jar;lib2.jar;"
Then Java can search lib1.jar and lib2.jar for class files.
The jar tool isn’t as useful as a zip utility. For example, you can’t add or
update files to an existing JAR file; you can create JAR files only from
scratch. Also, you can’t move files into a JAR file, erasing them as they are
moved. However, a JAR file created on one platform will be transparently
readable by the jar tool on any other platform (a problem that sometimes
plagues zip utilities).
As you will see in Chapter 13, JAR files are also used to package
JavaBeans.
Object serialization
Java’s object serialization allows you to take any object that implements
the Serializable interface and turn it into a sequence of bytes that can
later be fully restored to regenerate the original object. This is even true
across a network, which means that the serialization mechanism
automatically compensates for differences in operating systems. That is,
you can create an object on a Windows machine, serialize it, and send it
across the network to a Unix machine where it will be correctly
reconstructed. You don’t have to worry about the data representations on
the different machines, the byte ordering, or any other details.
The point of all this was to make something reasonably complex that
couldn’t easily be serialized. The act of serializing, however, is quite
simple. Once the ObjectOutputStream is created from some other
stream, writeObject( ) serializes the object. Notice the call to
writeObject( ) for a String, as well. You can also write all the primitive
data types using the same methods as DataOutputStream (they share
the same interface).
There are two separate code sections that look similar. The first writes
and reads a file and the second, for variety, writes and reads a
ByteArray. You can read and write an object using serialization to any
DataInputStream or DataOutputStream including, as you will see in
the Chapter 15, a network. The output from one run was:
Worm constructor: 6
Worm constructor: 5
Worm constructor: 4
Worm constructor: 3
Worm constructor: 2
Worm constructor: 1
w = :a(262):b(100):c(396):d(480):e(316):f(398)
Worm storage, w2 =
:a(262):b(100):c(396):d(480):e(316):f(398)
Worm storage, w3 =
:a(262):b(100):c(396):d(480):e(316):f(398)
You can see that the deserialized object really does contain all of the links
that were in the original object.
Once the program is compiled and run, copy the resulting X.file to a
subdirectory called xfiles, where the following code goes:
//: c11:xfiles:ThawAlien.java
// Try to recover a serialized file without the
// class of object that's stored in that file.
import java.io.*;
If you expect to do much after you’ve recovered an object that has been
serialized, you must make sure that the JVM can find the associated
.class file either in the local class path or somewhere on the Internet.
Controlling serialization
As you can see, the default serialization mechanism is trivial to use. But
what if you have special needs? Perhaps you have special security issues
and you don’t want to serialize portions of your object, or perhaps it just
Here’s an example that shows what you must do to fully store and retrieve
an Externalizable object:
//: c11:Blip3.java
// Reconstructing an externalizable object.
import java.io.*;
import java.util.*;
So to make things work correctly you must not only write the important
data from the object during the writeExternal( ) method (there is no
default behavior that writes any of the member objects for an
Externalizable object), but you must also recover that data in the
readExternal( ) method. This can be a bit confusing at first because the
default construction behavior for an Externalizable object can make it
seem like some kind of storage and retrieval takes place automatically. It
does not.
One way to prevent sensitive parts of your object from being serialized is
to implement your class as Externalizable, as shown previously. Then
nothing is automatically serialized and you can explicitly serialize only the
necessary parts inside writeExternal( ).
You can also see that the date field is stored to and recovered from disk
and not generated anew.
An alternative to Externalizable
If you’re not keen on implementing the Externalizable interface, there’s
another approach. You can implement the Serializable interface and
add (notice I say “add” and not “override” or “implement”) methods
private void
readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException
From a design standpoint, things get really weird here. First of all, you
might think that because these methods are not part of a base class or the
Serializable interface, they ought to be defined in their own interface(s).
But notice that they are defined as private, which means they are to be
called only by other members of this class. However, you don’t actually
call them from other members of this class, but instead the
writeObject( ) and readObject( ) methods of the
ObjectOutputStream and ObjectInputStream objects call your
object’s writeObject( ) and readObject( ) methods. (Notice my
tremendous restraint in not launching into a long diatribe about using the
same method names here. In a word: confusing.) You might wonder how
the ObjectOutputStream and ObjectInputStream objects have
access to private methods of your class. We can only assume that this is
part of the serialization magic.
There’s one other twist. Inside your writeObject( ), you can choose to
perform the default writeObject( ) action by calling
defaultWriteObject( ). Likewise, inside readObject( ) you can call
defaultReadObject( ). Here is a simple example that demonstrates how
you can control the storage and retrieval of a Serializable object:
//: c11:SerialCtl.java
// Controlling serialization by adding your own
// writeObject() and readObject() methods.
import java.io.*;
If you are going to use the default mechanism to write the non-transient
parts of your object, you must call defaultWriteObject( ) as the first
operation in writeObject( ) and defaultReadObject( ) as the first
operation in readObject( ). These are strange method calls. It would
appear, for example, that you are calling defaultWriteObject( ) for an
ObjectOutputStream and passing it no arguments, and yet it somehow
turns around and knows the reference to your object and how to write all
the non-transient parts. Spooky.
The storage and retrieval of the transient objects uses more familiar
code. And yet, think about what happens here. In main( ), a SerialCtl
object is created, and then it’s serialized to an ObjectOutputStream.
(Notice in this case that a buffer is used instead of a file—it’s all the same
to the ObjectOutputStream.) The serialization occurs in the line:
o.writeObject(sc);
The writeObject( ) method must be examining sc to see if it has its own
writeObject( ) method. (Not by checking the interface—there isn’t one—
Versioning
It’s possible that you might want to change the version of a serializable
class (objects of the original class might be stored in a database, for
example). This is supported but you’ll probably do it only in special cases,
and it requires an extra depth of understanding that we will not attempt
to achieve here. The JDK HTML documents downloadable from
java.sun.com cover this topic quite thoroughly.
You will also notice in the JDK HTML documentation many comments
that begin with:
Using persistence
It’s quite appealing to use serialization technology to store some of the
state of your program so that you can easily restore the program to the
current state later. But before you can do this, some questions must be
answered. What happens if you serialize two objects that both have a
reference to a third object? When you restore those two objects from their
serialized state, do you get only one occurrence of the third object? What
if you serialize your two objects to separate files and deserialize them in
different parts of your code?
ByteArrayOutputStream buf1 =
new ByteArrayOutputStream();
ObjectOutputStream o1 =
new ObjectOutputStream(buf1);
o1.writeObject(animals);
o1.writeObject(animals); // Write a 2nd set
// Write to a different stream:
ByteArrayOutputStream buf2 =
new ByteArrayOutputStream();
ObjectOutputStream o2 =
new ObjectOutputStream(buf2);
o2.writeObject(animals);
In main( ), one ArrayList is used to hold the Class objects and the
other to hold the shapes. If you don’t provide a command line argument
the shapeTypes ArrayList is created and the Class objects are added,
and then the shapes ArrayList is created and Shape objects are added.
Next, all the static color values are set to GREEN, and everything is
serialized to the file CADState.out.
3. Add calls to the new serialize and deserialize static methods in the
shapes.
Another issue you might have to think about is security, since serialization
also saves private data. If you have a security issue, those fields should
be marked as transient. But then you have to design a secure way to
Tokenizing input
Tokenizing is the process of breaking a sequence of characters into a
sequence of “tokens,” which are bits of text delimited by whatever you
choose. For example, your tokens could be words, and then they would be
delimited by white space and punctuation. There are two classes provided
in the standard Java library that can be used for tokenization:
StreamTokenizer and StringTokenizer.
StreamTokenizer
Although StreamTokenizer is not derived from InputStream or
OutputStream, it works only with InputStream objects, so it rightfully
belongs in the I/O portion of the library.
class Counter {
private int i = 1;
int read() { return i; }
void increment() { i++; }
}
To open the file, a FileReader is used, and to turn the file into words a
StreamTokenizer is created from the FileReader wrapped in a
BufferedReader. In StreamTokenizer, there is a default list of
separators, and you can add more with a set of methods. Here,
ordinaryChar( ) is used to say “This character has no significance that
I’m interested in,” so the parser doesn’t include it as part of any of the
words that it creates. For example, saying st.ordinaryChar('.') means
that periods will not be included as parts of the words that are parsed. You
In countWords( ), the tokens are pulled one at a time from the stream,
and the ttype information is used to determine what to do with each
token, since a token can be an end-of-line, a number, a string, or a single
character.
In main( ) you can see the use of a WordCount to open and count the
words in a file—it just takes two lines of code. Then an Iterator to a sorted
list of keys (words) is extracted, and this is used to pull out each key and
associated Count. The call to cleanup( ) is necessary to ensure that the
file is closed.
StringTokenizer
Although it isn’t part of the I/O library, the StringTokenizer has
sufficiently similar functionality to StreamTokenizer that it will be
described here.
You ask a StringTokenizer object for the next token in the string using
the nextToken( ) method, which either returns the token or an empty
string to indicate that no tokens remain.
The logic of the rest of the analyze( ) method is that the pattern that’s
being searched for is “I am sad,” “I am not happy,” or “Are you sad?”
Without the break statement, the code for this would be even messier
than it is. You should be aware that a typical parser (this is a primitive
example of one) normally has a table of these tokens and a piece of code
that moves through the states in the table as new tokens are read.
For the program to operate correctly, you must first build a class name
repository to hold all the class names in the standard Java library. You do
this by moving into all the source code subdirectories for the standard
To use the program to check your code, hand it the path and name of the
repository to use. It will check all the classes and identifiers in the current
directory and tell you which ones don’t follow the typical Java
capitalization style.
You should be aware that the program isn’t perfect; there are a few times
when it will point out what it thinks is a problem but on looking at the
code you’ll see that nothing needs to be changed. This is a little annoying,
but it’s still much easier than trying to find all these cases by staring at
your code.
//: c11:ClassScanner.java
// Scans all files in directory for classes
// and identifiers, to check capitalization.
// Assumes properly compiling code listings.
// Doesn't do everything right, but is a
// useful aid.
import java.io.*;
import java.util.*;
To keep life simple, the class names from the standard Java libraries are
all put into a Properties object (from the standard Java library).
Remember that a Properties object is a HashMap that holds only
String objects for both the key and value entries. However, it can be
saved to disk and restored from disk in one method call, so it’s ideal for
the repository of names. Actually, we need only a list of names, and a
HashMap can’t accept null for either its key or its value entry. So the
same object will be used for both the key and the value.
For the classes and identifiers that are discovered for the files in a
particular directory, two MultiStringMaps are used: classMap and
identMap. Also, when the program starts up it loads the standard class
name repository into the Properties object called classes, and when a
new class name is found in the local directory that is also added to
classes as well as to classMap. This way, classMap can be used to step
through all the classes in the local directory, and classes can be used to
see if the current token is a class name (which indicates a definition of an
object or method is beginning, so grab the next tokens—until a
semicolon—and put them into identMap).
Inside scanListing( ) the source code file is opened and turned into a
StreamTokenizer. In the documentation, passing true to
slashStarComments( ) and slashSlashComments( ) is supposed to
strip those comments out, but this seems to be a bit flawed, as it doesn’t
quite work. Instead, those lines are commented out and the comments are
extracted by another method. To do this, the “/” must be captured as an
ordinary character rather than letting the StreamTokenizer absorb it as
part of a comment, and the ordinaryChar( ) method tells the
StreamTokenizer to do this. This is also true for dots (“.”), since we
want to have the method calls pulled apart into individual identifiers.
However, the underscore, which is ordinarily treated by
StreamTokenizer as an individual character, should be left as part of
identifiers since it appears in such static final values as TT_EOF, etc.,
used in this very program. The wordChars( ) method takes a range of
characters you want to add to those that are left inside a token that is
being parsed as a word. Finally, when parsing for one-line comments or
discarding a line we need to know when an end-of-line occurs, so by
calling eolIsSignificant(true) the EOL will show up rather than being
absorbed by the StreamTokenizer.
The rest of scanListing( ) reads and reacts to tokens until the end of the
file, signified when nextToken( ) returns the final static value
StreamTokenizer.TT_EOF.
If the word is class or interface then the next token represents a class or
interface name, and it is put into classes and classMap. If the word is
import or package, then we don’t want the rest of the line. Anything
else must be an identifier (which we’re interested in) or a keyword (which
we’re not, but they’re all lowercase anyway so it won’t spoil things to put
those in). These are added to identMap.
The next two methods are the ones in which the actual checking takes
place. In checkClassNames( ), the class names are extracted from the
classMap (which, remember, contains only the names in this directory,
organized by file name so the file name can be printed along with the
errant class name). This is accomplished by pulling each associated
ArrayList and stepping through that, looking to see if the first character
is lowercase. If so, the appropriate error message is printed.
Whether you’re building a repository or using one, you must try to open
the existing repository. By making a File object and testing for existence,
you can decide whether to open the file and load( ) the Properties list
classes inside ClassScanner. (The classes from the repository add to,
rather than overwrite, the classes found by the ClassScanner
constructor.) If you provide only one command-line argument it means
that you want to perform a check of the class names and identifier names,
but if you provide two arguments (the second being “-a”) you’re building a
class name repository. In this case, an output file is opened and the
method Properties.save( ) is used to write the list into a file, along with
a string that provides header file information.
Summary
The Java I/O stream library does satisfy the basic requirements: you can
perform reading and writing with the console, a file, a block of memory,
or even across the Internet (as you will see in Chapter 15). With
inheritance, you can create new types of input and output objects. And
you can even add a simple extensibility to the kinds of objects a stream
will accept by redefining the toString( ) method that’s automatically
called when you pass an object to a method that’s expecting a String
(Java’s limited “automatic type conversion”).
However, once you do understand the decorator pattern and begin using
the library in situations that require its flexibility, you can begin to benefit
from this design, at which point its cost in extra lines of code may not
bother you as much.
If you do not find what you’re looking for in this chapter (which has only
been an introduction, and is not meant to be comprehensive), you can
find in-depth coverage in Java I/O, by Elliotte Rusty Harold (O’Reilly,
1999).
Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in Java
Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
1. Open a text file so that you can read the file one line at a time.
Read each line as a String and place that String object into a
LinkedList. Print all of the lines in the LinkedList in reverse
order.
2. Modify Exercise 1 so that the name of the file you read is provided
as a command-line argument.
3. Modify Exercise 2 to also open a text file so you can write text into
it. Write the lines in the ArrayList, along with line numbers (do
not attempt to use the “LineNumber” classes), out to the file.
This chapter looks at the ways that Java allows you to discover
information about objects and classes at run-time. This takes two forms:
“traditional” RTTI, which assumes that you have all the types available at
compile-time and run-time, and the “reflection” mechanism, which allows
you to discover class information solely at run-time. The “traditional”
RTTI will be covered first, followed by a discussion of reflection.
Shape
draw()
659
This is a typical class hierarchy diagram, with the base class at the top and
the derived classes growing downward. The normal goal in object-
oriented programming is for the bulk of your code to manipulate
references to the base type (Shape, in this case), so if you decide to
extend the program by adding a new class (Rhomboid, derived from
Shape, for example), the bulk of the code is not affected. In this example,
the dynamically bound method in the Shape interface is draw( ), so the
intent is for the client programmer to call draw( ) through a generic
Shape reference. draw( ) is overridden in all of the derived classes, and
because it is a dynamically bound method, the proper behavior will occur
even though it is called through a generic Shape reference. That’s
polymorphism.
class Shape {
void draw() {
System.out.println(this + ".draw()");
}
}
At the point you fetch an element out of the ArrayList with next( ),
things get a little busy. Since ArrayList holds only Objects, next( )
naturally produces an Object reference. But we know it’s really a
Shape reference, and we want to send Shape messages to that object. So
a cast to Shape is necessary using the traditional “(Shape)” cast. This is
the most basic form of RTTI, since in Java all casts are checked at run-
time for correctness. That’s exactly what RTTI means: at run-time, the
type of an object is identified.
In this case, the RTTI cast is only partial: the Object is cast to a Shape,
and not all the way to a Circle, Square, or Triangle. That’s because the
only thing we know at this point is that the ArrayList is full of Shapes.
Now polymorphism takes over and the exact method that’s called for the
Shape is determined by whether the reference is for a Circle, Square,
or Triangle. And in general, this is how it should be; you want the bulk of
your code to know as little as possible about specific types of objects, and
to just deal with the general representation of a family of objects (in this
case, Shape). As a result, your code will be easier to write, read, and
maintain, and your designs will be easier to implement, understand, and
change. So polymorphism is the general goal in object-oriented
programming.
There’s a Class object for each class that is part of your program. That is,
each time you write and compile a new class, a single Class object is also
created (and stored, appropriately enough, in an identically named .class
file). At run-time, when you want to make an object of that class, the Java
Virtual Machine (JVM) that’s executing your program first checks to see if
the Class object for that type is loaded. If not, the JVM loads it by finding
the .class file with that name. Thus, a Java program isn’t completely
loaded before it begins, which is different from many traditional
languages.
class Candy {
static {
System.out.println("Loading Candy");
}
}
class Gum {
static {
System.out.println("Loading Gum");
}
}
class Cookie {
static {
System.out.println("Loading Cookie");
}
}
Class literals
Java provides a second way to produce the reference to the Class object,
using a class literal. In the above program this would look like:
Gum.class;
which is not only simpler, but also safer since it’s checked at compile-
time. Because it eliminates the method call, it’s also more efficient.
… is equivalent to …
boolean.class Boolean.TYPE
char.class Character.TYPE
byte.class Byte.TYPE
short.class Short.TYPE
int.class Integer.TYPE
long.class Long.TYPE
float.class Float.TYPE
double.class Double.TYPE
void.class Void.TYPE
My preference is to use the “.class” versions if you can, since they’re more
consistent with regular classes.
1. The classic cast; e.g., “(Shape),” which uses RTTI to make sure the
cast is correct and throws a ClassCastException if you’ve
performed a bad cast.
2. The Class object representing the type of your object. The Class
object can be queried for useful run-time information.
In C++, the classic cast “(Shape)” does not perform RTTI. It simply tells
the compiler to treat the object as the new type. In Java, which does
perform the type check, this cast is often called a “type safe downcast.”
There’s a third form of RTTI in Java. This is the keyword instanceof that
tells you if an object is an instance of a particular type. It returns a
boolean so you use it in the form of a question, like this:
if(x instanceof Dog)
((Dog)x).bark();
The above if statement checks to see if the object x belongs to the class
Dog before casting x to a Dog. It’s important to use instanceof before a
downcast when you don’t have other information that tells you the type of
the object; otherwise you’ll end up with a ClassCastException.
Ordinarily, you might be hunting for one type (triangles to turn purple,
for example), but you can easily tally all of the objects using instanceof.
Suppose you have a family of Pet classes:
//: c12:Pets.java
class Pet {}
class Dog extends Pet {}
class Pug extends Dog {}
class Cat extends Pet {}
class Rodent extends Pet {}
class Gerbil extends Rodent {}
class Hamster extends Rodent {}
You can also see that the creation of petTypes does not need to be
surrounded by a try block since it’s evaluated at compile-time and thus
won’t throw any exceptions, unlike Class.forName( ).
When the Pet objects are dynamically created, you can see that the
random number is restricted so it is between one and petTypes.length
A dynamic instanceof
The Class isInstance method provides a way to dynamically call the
instanceof operator. Thus, all those tedious instanceof statements can
be removed in the PetCount example:
//: c12:PetCount3.java
// Using isInstance().
import java.util.*;
class Base {}
class Derived extends Base {}
RTTI syntax
Java performs its RTTI using the Class object, even if you’re doing
something like a cast. The class Class also has a number of other ways
you can use RTTI.
First, you must get a reference to the appropriate Class object. One way
to do this, as shown in the previous example, is to use a string and the
Class.forName( ) method. This is convenient because you don’t need an
object of that type in order to get the Class reference. However, if you do
already have an object of the type you’re interested in, you can fetch the
Class reference by calling a method that’s part of the Object root class:
getClass( ). This returns the Class reference representing the actual
type of the object. Class has many interesting methods, demonstrated in
the following example:
interface HasBatteries {}
interface Waterproof {}
interface ShootsThings {}
class Toy {
// Comment out the following default
// constructor to see
// NoSuchMethodError from (*1*)
Toy() {}
Toy(int i) {}
}
If you have a Class object you can also ask it for its direct base class using
getSuperclass( ). This, of course, returns a Class reference that you can
further query. This means that, at run-time, you can discover an object’s
entire class hierarchy.
The newInstance( ) method of Class can, at first, seem like just another
way to clone( ) an object. However, you can create a new object with
newInstance( ) without an existing object, as seen here, because there
is no Toy object—only cy, which is a reference to y’s Class object. This is
a way to implement a “virtual constructor,” which allows you to say “I
don’t know exactly what type you are, but create yourself properly
anyway.” In the example above, cy is just a Class reference with no
further type information known at compile-time. And when you create a
new instance, you get back an Object reference. But that reference is
pointing to a Toy object. Of course, before you can send any messages
other than those accepted by Object, you have to investigate it a bit and
Reflection: run-time
class information
If you don’t know the precise type of an object, RTTI will tell you.
However, there’s a limitation: the type must be known at compile-time in
order for you to be able to detect it using RTTI and do something useful
with the information. Put another way, the compiler must know about all
the classes you’re working with for RTTI.
This doesn’t seem like that much of a limitation at first, but suppose
you’re given a reference to an object that’s not in your program space. In
fact, the class of the object isn’t even available to your program at
compile-time. For example, suppose you get a bunch of bytes from a disk
file or from a network connection and you’re told that those bytes
represent a class. Since the compiler can’t know about the class while it’s
compiling the code, how can you possibly use such a class?
It’s important to realize that there’s nothing magic about reflection. When
you’re using reflection to interact with an object of an unknown type, the
JVM will simply look at the object and see that it belongs to a particular
class (just like ordinary RTTI) but then, before it can do anything else, the
Class object must be loaded. Thus, the .class file for that particular type
must still be available to the JVM, either on the local machine or across
the network. So the true difference between RTTI and reflection is that
with RTTI, the compiler opens and examines the .class file at compile-
time. Put another way, you can call all the methods of an object in the
“normal” way. With reflection, the .class file is unavailable at compile-
time; it is opened and examined by the run-time environment.
1 Especially in the past. However, Sun has greatly improved its HTML Java documentation
so that it’s easier to see base-class methods.
The output for ShowMethods is still a little tedious. For example, here’s
a portion of the output produced by invoking java ShowMethods
java.lang.String:
public boolean
The new version of the program uses the above class to clean up the
output:
//: c12:ShowMethodsClean.java
// ShowMethods with the qualifiers stripped
// to make the results easier to read.
import java.lang.reflect.*;
import com.bruceeckel.util.*;
This tool can be a real time-saver while you’re programming, when you
can’t remember if a class has a particular method and you don’t want to
go walking through the class hierarchy in the online documentation, or if
you don’t know whether that class can do anything with, for example,
Color objects.
Summary
RTTI allows you to discover type information from an anonymous base-
class reference. Thus, it’s ripe for misuse by the novice since it might
make sense before polymorphic method calls do. For many people coming
from a procedural background, it’s difficult not to organize their programs
into sets of switch statements. They could accomplish this with RTTI and
thus lose the important value of polymorphism in code development and
maintenance. The intent of Java is that you use polymorphic method calls
throughout your code, and you use RTTI only when you must.
Putting a feature in a base class might mean that, for the benefit of one
particular class, all of the other classes derived from that base require
some meaningless stub of a method. This makes the interface less clear
and annoys those who must override abstract methods when they derive
from that base class. For example, consider a class hierarchy representing
musical instruments. Suppose you wanted to clear the spit valves of all the
appropriate instruments in your orchestra. One option is to put a
clearSpitValve( ) method in the base class Instrument, but this is
confusing because it implies that Percussion and Electronic
instruments also have spit valves. RTTI provides a much more reasonable
Finally, RTTI will sometimes solve efficiency problems. If your code nicely
uses polymorphism, but it turns out that one of your objects reacts to this
general purpose code in a horribly inefficient way, you can pick out that
type using RTTI and write case-specific code to improve the efficiency. Be
wary, however, of programming for efficiency too soon. It’s a seductive
trap. It’s best to get the program working first, then decide if it’s running
fast enough, and only then should you attack efficiency issues—with a
profiler.
Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in Java
Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
The situation improved with the Java 1.1 AWT event model, which takes a
much clearer, object-oriented approach, along with the addition of
JavaBeans, a component programming model that is oriented toward the
easy creation of visual programming environments. Java 2 finishes the
transformation away from the old Java 1.0 AWT by essentially replacing
everything with the Java Foundation Classes (JFC), the GUI portion of
which is called “Swing.” These are a rich set of easy-to-use, easy-to-
1 A variation on this is called “the principle of least astonishment,” which essentially says:
“don’t surprise the user.”
689
understand JavaBeans that can be dragged and dropped (as well as hand
programmed) to create a GUI that you can (finally) be satisfied with. The
“revision 3” rule of the software industry (a product isn’t good until
revision 3) seems to hold true with programming languages as well.
This chapter does not cover anything but the modern, Java 2 Swing
library, and makes the reasonable assumption that Swing is the final
destination GUI library for Java. If for some reason you need to use the
original “old” AWT (because you’re supporting old code or you have
browser limitations), you can find that introduction in the first edition of
this book, downloadable at www.BruceEckel.com (also included on the
CD ROM bound with this book).
Early in this chapter, you’ll see how things are different when you want to
create an applet vs. a regular application using Swing, and how to create
programs that are both applets and applications so they can be run either
inside a browser or from the command line. Almost all the GUI examples
in this book will be executable as either applets or applications.
Please be aware that this is not a comprehensive glossary of either all the
Swing components, or all the methods for the described classes. What you
see here is intended to be simple. The Swing library is vast, and the goal of
this chapter is only to get you started with the essentials and comfortable
with the concepts. If you need to do more, then Swing can probably give
you what you want if you’re willing to do the research.
I assume here that you have downloaded and installed the (free) Java
library documents in HTML format from java.sun.com and will browse
the javax.swing classes in that documentation to see the full details and
methods of the Swing library. Because of the simplicity of the Swing
design, this will often be enough information to solve your problem. There
are numerous (rather thick) books dedicated solely to Swing and you’ll
want to go to those if you need more depth, or if you want to modify the
default Swing behavior.
Swing contains all the components that you expect to see in a modern UI,
everything from buttons that contain pictures to trees and tables. It’s a big
library, but it’s designed to have appropriate complexity for the task at
hand—if something is simple, you don’t have to write much code but as
you try to do more complex things, your code becomes proportionally
more complex. This means an easy entry point, but you’ve got the power if
you need it.
For speed, all the components are “lightweight,” and Swing is written
entirely in Java for portability.
Swing also supports a rather radical feature called “pluggable look and
feel,” which means that the appearance of the UI can be dynamically
changed to suit the expectations of users working under different
platforms and operating systems. It’s even possible (albeit difficult) to
invent your own look and feel.
Applet restrictions
Programming within an applet is so restrictive that it’s often referred to as
being “inside the sandbox,” since you always have someone—that is, the
Java run-time security system—watching over you.
However, you can also step outside the sandbox and write regular
applications rather than applets, in which case you can access the other
features of your OS. We’ve been writing regular applications all along in
this book, but they’ve been console applications without any graphical
components. Swing can also be used to build GUI interfaces for regular
applications.
1. An applet can’t touch the local disk. This means writing or reading,
since you wouldn’t want an applet to read and transmit private
information over the Internet without your permission. Writing is
prevented, of course, since that would be an open invitation to a
virus. Java offers digital signing for applets. Many applet
2. Applets can take longer to display, since you must download the
whole thing every time, including a separate server hit for each
different class. Your browser can cache the applet, but there are no
guarantees. Because of this, you should always package your
applets in a JAR (Java ARchive) file that combines all the applet
components (including other .class files as well as images and
sounds) together into a single compressed file that can be
downloaded in a single server transaction. “Digital signing” is
available for each individual entry in the JAR file.
Applet advantages
If you can live within the restrictions, applets have definite advantages,
especially when building client/server or other networked applications:
Applets are built using an application framework. You inherit from class
JApplet and override the appropriate methods. There are a few methods
that control the creation and execution of an applet on a Web page:
Method Operation
init( ) Automatically called to perform first-time initialization
of the applet, including component layout. You’ll always
override this method.
start( ) Called every time the applet moves into sight on the
Web browser to allow the applet to start up its normal
operations (especially those that are shut off by
stop( )). Also called after init( ).
stop( ) Called every time the applet moves out of sight on the
Web browser to allow the applet to shut off expensive
operations. Also called right before destroy( ).
destroy( ) Called when the applet is being unloaded from the page
to perform final release of resources when the applet is
no longer used
In this program, the only activity is putting a text label on the applet, via
the JLabel class (the old AWT appropriated the name Label as well as
other names of components, so you will often see a leading “J” used with
Swing components). The constructor for this class takes a String and
uses it to create the label. In the above program this label is placed on the
form.
The init( ) method is responsible for putting all the components on the
form using the add( ) method. You might think that you ought to be able
to simply call add( ) by itself, and in fact that’s the way it used to be in the
old AWT. However, Swing requires you to add all components to the
“content pane” of a form, and so you must call getContentPane( ) as
part of the add( ) process.
This process used to be very simple, when Java itself was simple and
everyone was on the same bandwagon and incorporated the same Java
support inside their Web browsers. Then you might have been able to get
away with a very simple bit of HTML inside your Web page, like this:
<applet code=Applet1 width=100 height=50>
</applet>
Then along came the browser and language wars, and we (programmers
and end users alike) lost. After awhile, JavaSoft realized that we could no
longer expect browsers to support the correct flavor of Java, and the only
solution was to provide some kind of add-on that would conform to a
browser’s extension mechanism. By using the extension mechanism
(which a browser vendor cannot disable—in an attempt to gain
competitive advantage—without breaking all the third-party extensions),
JavaSoft guarantees that Java cannot be shut out of the Web browser by
an antagonistic vendor.
3 It is assumed that the reader is familiar with the basics of HTML. It’s not too hard to
figure out, and there are lots of books and resources.
4 This page—in particular, the ‘clsid’ portion—seemed to work fine with both JDK1.2.2 and
JDK1.3 rc-1. However, you may find that you have to change the tag sometime in the
future. Details can be found at java.sun.com.
The code value gives the name of the .class file where the applet resides.
The width and height specify the initial size of the applet (in pixels, as
before). There are other items you can place within the applet tag: a place
to find other .class files on the Internet (codebase), alignment
information (align), a special identifier that makes it possible for applets
to communicate with each other (name), and applet parameters to
provide information that the applet can retrieve. Parameters are in the
form:
<param name="identifier" value = "information">
and there can be as many as you want.
The source code package for this book provides an HTML page for each of
the applets in this book, and thus many examples of the applet tag. You
can find a full and current description of the details of placing applets in
Web pages at java.sun.com.
Testing applets
You can perform a simple test without any network connection by starting
up your Web browser and opening the HTML file containing the applet
tag. As the HTML file is loaded, the browser will discover the applet tag
and go hunt for the .class file specified by the code value. Of course, it
When you want to try this out on your Web site things are a little more
complicated. First of all, you must have a Web site, which for most people
means a third-party Internet Service Provider (ISP) at a remote location.
Since the applet is just a file or set of files, the ISP does not have to
provide any special support for Java. You must also have a way to move
the HTML files and the .class files from your site to the correct directory
on the ISP machine. This is typically done with a File Transfer Protocol
(FTP) program, of which there are many different types available for free
or as shareware. So it would seem that all you need to do is move the files
to the ISP machine with FTP, then connect to the site and HTML file
using your browser; if the applet comes up and works, then everything
checks out, right?
Here’s where you can get fooled. If the browser on the client machine
cannot locate the .class file on the server, it will hunt through the
CLASSPATH on your local machine. Thus, the applet might not be
loading properly from the server, but to you it looks fine during your
testing process because the browser finds it on your machine. When
someone else connects, however, his or her browser can’t find it. So when
you’re testing, make sure you erase the relevant .class files (or .jar file)
on your local machine to verify that they exist in the proper location on
the server.
The Swing library allows you to make an application that preserves the
look and feel of the underlying operating environment. If you want to
build windowed applications, it makes sense to do so5 only if you can use
the latest version of Java and associated tools so you can deliver
applications that won’t confound your users. If for some reason you’re
forced to use an older version of Java, think hard before committing to
building a significant windowed application.
Often you’ll want to be able to create a class that can be invoked as either
a window or an applet. This is especially convenient when you’re testing
the applets, since it’s typically much faster and easier to run the resulting
applet-application from the command line than it is to start up a Web
browser or the Appletviewer.
To create an applet that can be run from the console command line, you
simply add a main( ) to your applet that builds an instance of the applet
inside a JFrame.6 As a simple example, let’s look at Applet1b.java
modified to work as both an application and an applet:
//: c13:Applet1c.java
5 In my opinion. And after you learn about Swing, you won’t want to waste your time on
the earlier stuff.
6 As described earlier, “Frame” was already taken by the AWT, so Swing uses JFrame.
The line:
Console.setupClosing(frame);
Causes the window to be properly closed. Console comes from
com.bruceeckel.swing and will be explained a little later.
You can see that in main( ), the applet is explicitly initialized and started
since in this case the browser isn’t available to do it for you. Of course,
this doesn’t provide the full behavior of the browser, which also calls
A display framework
Although the code that turns programs into both applets and applications
produces valuable results, if used everywhere it becomes distracting and
wastes paper. Instead, the following display framework will be used for
the Swing examples in the rest of this book:
//: com:bruceeckel:swing:Console.java
// Tool for running Swing demos from the
// console, both applets and JFrames.
package com.bruceeckel.swing;
import javax.swing.*;
import java.awt.event.*;
7 This will make sense after you’ve read further in this chapter. First, make the reference
JApplet a static member of the class (instead of a local variable of main( )), and then
call applet.stop( ) and applet.destroy( ) inside
WindowAdapter.windowClosing( ) before you call System.exit( ).
Now any applet can be run from the console by creating a main( )
containing a line like this:
Console.run(new MyClass(), 500, 300);
in which the last two arguments are the display width and height. Here’s
Applet1c.java modified to use Console:
//: c13:Applet1d.java
// Console runs applets from the command line.
// <applet code=Applet1d width=100 height=50>
// </applet>
import javax.swing.*;
import java.awt.*;
import com.bruceeckel.swing.*;
Next, create the following script without the first and last lines (this script
is part of this book’s source-code package):
//:! c13:RunJava.bat
@rem = '--*-Perl-*--
@echo off
perl -x -S "%0" %1 %2 %3 %4 %5 %6 %7 %8 %9
goto endofperl
@rem ';
#!perl
$file = $ARGV[0];
$file =~ s/(.*)\..*/\1/;
$file =~ s/(.*\\)*(.*)/$+/;
`java $file`;
__END__
:endofperl
///:~
Once you perform this installation, you may run any Java program by
simply double-clicking on the .class file containing a main( ).
Making a button
Making a button is quite simple: you just call the JButton constructor
with the label you want on the button. You’ll see later that you can do
fancier things, like putting graphic images on buttons.
Usually you’ll want to create a field for the button inside your class so that
you can refer to it later.
Capturing an event
You’ll notice that if you compile and run the applet above, nothing
happens when you press the buttons. This is where you must step in and
write some code to determine what will happen. The basis of event-driven
programming, which comprises a lot of what a GUI is about, is tying
events to code that responds to those events.
At first, we will just focus on the main event of interest for the
components being used. In the case of a JButton, this “event of interest”
is that the button is pressed. To register your interest in when a button is
pressed, you call the JButton’s addActionListener( ) method. This
method expects an argument that is an object that implements the
ActionListener interface, which contains a single method called
actionPerformed( ). So all you have to do to attach code to a JButton
is to implement the ActionListener interface in a class, and register an
object of that class with the JButton via addActionListener( ). The
method will be called when the button is pressed (this is normally referred
to as a callback).
But what should the result of pressing that button be? We’d like to see
something change on the screen, so a new Swing component will be
introduced: the JTextField. This is a place where text can be typed, or in
this case modified by the program. Although there are a number of ways
to create a JTextField, the simplest is just to tell the constructor how
wide you want that field to be. Once the JTextField is placed on the
form, you can modify its contents by using the setText( ) method (there
are many other methods in JTextField, but you must look these up in
the HTML documentation for the JDK from java.sun.com). Here is what
it looks like:
//: c13:Button2.java
// Responding to button presses.
// <applet code=Button2 width=200 height=75>
// </applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;
Controlling layout
The way that you place components on a form in Java is probably
different from any other GUI system you’ve used. First, it’s all code; there
are no “resources” that control placement of components. Second, the way
components are placed on a form is controlled not by absolute positioning
but by a “layout manager” that decides how the components lie based on
the order that you add( ) them. The size, shape, and placement of
BorderLayout
The applet uses a default layout scheme: the BorderLayout (a number
of the previous example have changed the layout manager to
FlowLayout). Without any other instruction, this takes whatever you
add( ) to it and places it in the center, stretching the object all the way
out to the edges.
FlowLayout
This simply “flows” the components onto the form, from left to right until
the top space is full, then moves down a row and continues flowing.
GridLayout
A GridLayout allows you to build a table of components, and as you add
them they are placed left-to-right and top-to-bottom in the grid. In the
constructor you specify the number of rows and columns that you need
and these are laid out in equal proportions.
//: c13:GridLayout1.java
// Demonstrates GridLayout.
// <applet code=GridLayout1
GridBagLayout
The GridBagLayout provides you with tremendous control in deciding
exactly how the regions of your window will lay themselves out and
reformat themselves when the window is resized. However, it’s also the
most complicated layout manager, and quite difficult to understand. It is
intended primarily for automatic code generation by a GUI builder (good
GUI builders will use GridBagLayout instead of absolute placement). If
your design is so complicated that you feel you need to use
GridBagLayout, then you should be using a GUI builder tool to
generate that design. If you feel you must know the intricate details, I’ll
refer you to Core Java 2 by Horstmann & Cornell (Prentice-Hall, 1999),
or a dedicated Swing book, as a starting point.
Absolute positioning
It is also possible to set the absolute position of the graphical components
in this way:
Some GUI builders use this approach extensively, but this is usually not
the best way to generate code. More useful GUI builders will use
GridBagLayout instead.
BoxLayout
Because people had so much trouble understanding and working with
GridBagLayout, Swing also includes the BoxLayout, which gives you
many of the benefits of GridBagLayout without the complexity, so you
can often use it when you need to do hand-coded layouts (again, if your
design becomes too complex, use a GUI builder that generates
GridBagLayouts for you). BoxLayout allows you to control the
placement of components either vertically or horizontally, and to control
the space between the components using something called “struts and
glue.” First, let’s see how to use the BoxLayout directly, in the same way
that the other layout managers have been demonstrated:
//: c13:BoxLayout1.java
// Vertical and horizontal BoxLayouts.
// <applet code=BoxLayout1
// width=450 height=200> </applet>
import javax.swing.*;
import java.awt.*;
import com.bruceeckel.swing.*;
All of your event logic, then, will go inside a listener class. When you
create a listener class, the sole restriction is that it must implement the
appropriate interface. You can create a global listener class, but this is a
situation in which inner classes tend to be quite useful, not only because
they provide a logical grouping of your listener classes inside the UI or
business logic classes they are serving, but because (as you shall see later)
the fact that an inner class object keeps a reference to its parent object
provides a nice way to call across class and subsystem boundaries.
All the examples so far in this chapter have been using the Swing event
model, but the remainder of this section will fill out the details of that
model.
You can see that each type of component supports only certain types of
events. It turns out to be rather difficult to look up all the events
supported by each component. A simpler approach is to modify the
ShowMethodsClean.java program from Chapter 12 so that it displays
all the event listeners supported by any Swing component that you enter.
8 There is no MouseMotionEvent even though it seems like there ought to be. Clicking
and motion is combined into MouseEvent, so this second appearance of MouseEvent
in the table is not an error.
However, by Chapter 12 you hadn’t seen Swing, so the tool in that chapter
was developed as a command-line application. Here is the more useful
GUI version, specialized to look for the “addListener” methods in Swing
components:
//: c13:ShowAddListeners.java
// Display the "addXXXListener" methods of any
// Swing class.
// <applet code = ShowAddListeners
// width=500 height=400></applet>
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.*;
import java.io.*;
import com.bruceeckel.swing.*;
import com.bruceeckel.util.*;
The GUI contains a JTextField name in which you can enter the Swing
class name you want to look up. The results are displayed in a
JTextArea.
1. Take the name of the event class and remove the word “Event.”
Add the word “Listener” to what remains. This is the listener
interface you must implement in your inner class.
This is not an exhaustive listing, partly because the event model allows
you to create your own event types and associated listeners. Thus, you’ll
regularly come across libraries that have invented their own events, and
the knowledge gained in this chapter will allow you to figure out how to
use these events.
To solve the problem, some (but not all) of the listener interfaces that
have more than one method are provided with adapters, the names of
which you can see in the table above. Each adapter provides default empty
methods for each of the interface methods. Then all you need to do is
inherit from the adapter and override only the methods you need to
change. For example, the typical WindowListener you’ll use looks like
this (remember that this has been wrapped inside the Console class in
com.bruceeckel.swing):
class MyWindowListener extends WindowAdapter {
public void windowClosing(WindowEvent e) {
System.exit(0);
9 In Java 1.0/1.1 you could not usefully inherit from the button object. This was only one of
numerous fundamental design flaws.
When report( ) is called it is given the name of the event and the
parameter string from the event. It uses the HashMap h in the outer
class to look up the actual JTextField associated with that event name,
and then places the parameter string into that field.
This example is fun to play with since you can really see what’s going on
with the events in your program.
You can easily see what each of these examples looks like while running
by viewing the HTML pages in the downloadable source code for this
chapter.
Keep in mind:
2. Because of the naming convention used for Swing events, it’s fairly
easy to guess how to write and install a handler for a particular type
of event. Use the lookup program ShowAddListeners.java from
earlier in this chapter to aid in your investigation of a particular
component.
Buttons
Swing includes a number of different types of buttons. All buttons, check
boxes, radio buttons, and even menu items are inherited from
AbstractButton (which, since menu items are included, would probably
have been better named “AbstractChooser” or something equally general).
You’ll see the use of menu items shortly, but the following example shows
the various types of buttons available:
//: c13:Buttons.java
// Various Swing buttons.
// <applet code=Buttons
Button groups
If you want radio buttons to behave in an “exclusive or” fashion, you must
add them to a “button group.” But, as the example below demonstrates,
any AbstractButton can be added to a ButtonGroup.
Icons
You can use an Icon inside a JLabel or anything that inherits from
AbstractButton (including JButton, JCheckBox, JRadioButton,
and the different kinds of JMenuItem). Using Icons with JLabels is
quite straightforward (you’ll see an example later). The following example
explores all the additional ways you can use Icons with buttons and their
descendants.
You can use any gif files you want, but the ones used in this example are
part of this book’s code distribution, available at www.BruceEckel.com.
To open a file and bring in the image, simply create an ImageIcon and
hand it the file name. From then on, you can use the resulting Icon in
your program.
Note that path information is hard-coded into this example; you will need
to change the path to correspond to the location of the image files.
//: c13:Faces.java
// Icon behavior in Jbuttons.
// <applet code=Faces
// width=250 height=100></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;
Tool tips
The previous example added a “tool tip” to the button. Almost all of the
classes that you’ll be using to create your user interfaces are derived from
JComponent, which contains a method called
setToolTipText(String). So, for virtually anything you place on your
form, all you need to do is say (for an object jc of any JComponent-
derived class):
jc.setToolTipText("My tip");
and when the mouse stays over that JComponent for a predetermined
period of time, a tiny box containing your text will pop up next to the
mouse.
Text fields
This example shows the extra behavior that JTextFields are capable of:
//: c13:TextFields.java
// Text fields and Java events.
// <applet code=TextFields width=375
// height=125></applet>
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import java.awt.*;
import java.awt.event.*;
Borders
JComponent contains a method called setBorder( ), which allows you
to place various interesting borders on any visible component. The
following example demonstrates a number of the different borders that
are available, using a method called showBorder( ) that creates a
JPanel and puts on the border in each case. Also, it uses RTTI to find the
name of the border that you’re using (stripping off all the path
information), then puts that name in a JLabel in the middle of the panel:
//: c13:Borders.java
// Different Swing borders.
// <applet code=Borders
// width=500 height=300></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.border.*;
import com.bruceeckel.swing.*;
JScrollPanes
Most of the time you’ll just want to let a JScrollPane do it’s job, but you
can also control which scroll bars are allowed—vertical, horizontal, both,
or neither:
//: c13:JScrollPanes.java
// Controlling the scrollbars in a JScrollPane.
// <applet code=JScrollPanes width=300 height=725>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
A mini-editor
The JTextPane control provides a great deal of support for editing,
without much effort. The following example makes very simple use of this,
ignoring the bulk of the functionality of the class:
//: c13:TextPane.java
// The JTextPane control is a little editor.
// <applet code=TextPane width=475 height=425>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;
import com.bruceeckel.util.*;
Check boxes
A check box provides a way to make a single on/off choice; it consists of a
tiny box and a label. The box typically holds a little “x” (or some other
indication that it is set) or is empty, depending on whether that item was
selected.
Radio buttons
The concept of a radio button in GUI programming comes from pre-
electronic car radios with mechanical buttons: when you push one in, any
other button that was pressed pops out. Thus, it allows you to force a
single choice among many.
Here’s a simple example of the use of radio buttons. Note that you capture
radio button events like all others:
//: c13:RadioButtons.java
// Using JRadioButtons.
// <applet code=RadioButtons
// width=200 height=100> </applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;
Java’s JComboBox box is not like the combo box in Windows, which lets
you select from a list or type in your own selection. With a JComboBox
box you choose one and only one element from the list. In the following
List boxes
List boxes are significantly different from JComboBox boxes, and not
just in appearance. While a JComboBox box drops down when you
activate it, a JList occupies some fixed number of lines on a screen all the
time and doesn’t change. If you want to see the items in a list, you simply
call getSelectedValues( ), which produces an array of String of the
items that have been selected.
You can see that borders have also been added to the lists.
If you just want to put an array of Strings into a JList, there’s a much
simpler solution: you pass the array to the JList constructor, and it builds
the list automatically. The only reason for using the “list model” in the
above example is so that the list could be manipulated during the
execution of the program.
Tabbed panes
The JTabbedPane allows you to create a “tabbed dialog,” which has file-
folder tabs running across one edge, and all you have to do is press a tab
to bring forward a different dialog.
//: c13:TabbedPane1.java
// Demonstrates the Tabbed Pane.
// <applet code=TabbedPane1
// width=350 height=200> </applet>
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;
When you run the program you’ll see that the JTabbedPane
automatically stacks the tabs if there are too many of them to fit on one
row. You can see this by resizing the window when you run the program
from the console command line.
Message boxes
Windowing environments commonly contain a standard set of message
boxes that allow you to quickly post information to the user or to capture
information from the user. In Swing, these message boxes are contained
in JOptionPane. You have many different possibilities (some quite
Unlike a system that uses resources, with Java and Swing you must hand
assemble all the menus in source code. Here is a very simple menu
example:
//: c13:SimpleMenus.java
// <applet code=SimpleMenus
// width=200 height=75> </applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;
As a more sophisticated example, here are the ice cream flavors again,
used to create menus. This example also shows cascading menus,
keyboard mnemonics, JCheckBoxMenuItems, and the way you can
dynamically change menus:
//: c13:Menus.java
// Submenus, checkbox menu items, swapping menus,
// mnemonics (shortcuts) and action commands.
// <applet code=Menus width=300
// height=100> </applet>
import javax.swing.*;
import java.awt.*;
This program creates not one but two JMenuBars to demonstrate that
menu bars can be actively swapped while the program is running. You can
see how a JMenuBar is made up of JMenus, and each JMenu is made
up of JMenuItems, JCheckBoxMenuItems, or even other JMenus
(which produce submenus). When a JMenuBar is assembled it can be
installed into the current program with the setJMenuBar( ) method.
Note that when the button is pressed, it checks to see which menu is
currently installed by calling getJMenuBar( ), then it puts the other
menu bar in its place.
When testing for “Open,” notice that spelling and capitalization are
critical, but Java signals no error if there is no match with “Open.” This
kind of string comparison is a source of programming errors.
The events for menus are a bit inconsistent and can lead to confusion:
JMenuItems use ActionListeners, but JCheckboxMenuItems use
ItemListeners. The JMenu objects can also support ActionListeners,
but that’s not usually helpful. In general, you’ll attach listeners to each
JMenuItem, JCheckBoxMenuItem, or JRadioButtonMenuItem,
but the example shows ItemListeners and ActionListeners attached
to the various menu components.
You can also see the use of setActionCommand( ). This seems a bit
strange because in each case the “action command” is exactly the same as
the label on the menu component. Why not just use the label instead of
this alternative string? The problem is internationalization. If you retarget
this program to another language, you want to change only the label in the
menu, and not change the code (which would no doubt introduce new
errors). So to make this easy for code that checks the text string associated
with a menu component, the “action command” can be immutable while
the menu label can change. All the code works with the “action
command,” so it’s unaffected by changes to the menu labels. Note that in
this program, not all the menu components are examined for their action
commands, so those that aren’t don’t have their action command set.
The FL listener is simple even though it’s handling all the different flavors
in the flavor menu. This approach is useful if you have enough simplicity
in your logic, but in general, you’ll want to take the approach used with
FooL, BarL, and BazL, in which they are each attached to only a single
menu component so no extra detection logic is necessary and you know
exactly who called the listener. Even with the profusion of classes
generated this way, the code inside tends to be smaller and the process is
more foolproof.
You can see that menu code quickly gets long-winded and messy. This is
another case where the use of a GUI builder is the appropriate solution. A
good tool will also handle the maintenance of the menus.
Pop-up menus
The most straightforward way to implement a JPopupMenu is to create
an inner class that extends MouseAdapter, then add an object of that
inner class to each component that you want to produce pop-up behavior:
//: c13:Popup.java
// Creating popup menus with Swing.
// <applet code=Popup
// width=300 height=200></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;
Drawing
In a good GUI framework, drawing should be reasonably easy—and it is,
in the Swing library. The problem with any drawing example is that the
calculations that determine where things go are typically a lot more
complicated that the calls to the drawing routines, and these calculations
are often mixed together with the drawing calls so it can seem that the
interface is more complicated than it actually is.
When I created this program, the bulk of my time was spent in getting the
sine wave to display. Once I did that, I thought it would be nice to be able
to dynamically change the number of cycles. My programming
experiences when trying to do such things in other languages made me a
bit reluctant to try this, but it turned out to be the easiest part of the
project. I created a JSlider (the arguments are the left-most value of the
JSlider, the right-most value, and the starting value, respectively, but
there are other constructors as well) and dropped it into the JApplet.
Then I looked at the HTML documentation and noticed that the only
listener was the addChangeListener, which was triggered whenever the
slider was changed enough for it to produce a different value. The only
method for this was the obviously named stateChanged( ), which
provided a ChangeEvent object so that I could look backward to the
source of the change and find the new value. By calling the sines object’s
setCycles( ), the new value was incorporated and the JPanel redrawn.
In general, you will find that most of your Swing problems can be solved
by following a similar process, and you’ll find that it’s generally quite
simple, even if you haven’t used a particular component before.
Dialog Boxes
A dialog box is a window that pops up out of another window. Its purpose
is to deal with some specific issue without cluttering the original window
with those details. Dialog boxes are heavily used in windowed
programming environments, but less frequently used in applets.
To create a dialog box, you inherit from JDialog, which is just another
kind of Window, like a JFrame. A JDialog has a layout manager
(which defaults to BorderLayout) and you add event listeners to deal
with events. One significant difference when windowClosing( ) is called
is that you don’t want to shut down the application. Instead, you release
You’ll see that anything that pops up out of an applet, including dialog
boxes, is “untrusted.” That is, you get a warning in the window that’s been
popped up. This is because, in theory, it would be possible to fool the user
into thinking that they’re dealing with a regular native application and to
get them to type in their credit card number, which then goes across the
Web. An applet is always attached to a Web page and visible within your
Web browser, while a dialog box is detached—so in theory, it could be
possible. As a result it is not so common to see an applet that uses a dialog
box.
File dialogs
Some operating systems have a number of special built-in dialog boxes to
handle the selection of things such as fonts, colors, printers, and the like.
Virtually all graphical operating systems support the opening and saving
of files, however, and so Java’s JFileChooser encapsulates these for easy
use.
For an “open file” dialog, you call showOpenDialog( ), and for a “save
file” dialog you call showSaveDialog( ). These commands don’t return
until the dialog is closed. The JFileChooser object still exists, so you can
The ActionListener adds a new JLabel to the form, which also contains
HTML text. However, this label is not added during init( ) so you must
call the container’s validate( ) method in order to force a re-layout of the
components (and thus the display of the new label).
Trees
Using a JTree can be as simple as saying:
add(new JTree(
new Object[] {"this", "that", "other"}));
This displays a primitive tree. The API for trees is vast, however—certainly
one of the largest in Swing. It appears that you can do just about anything
with trees, but more sophisticated tasks might require quite a bit of
research and experimentation.
The JTree is controlled through its model. When you make a change to
the model, the model generates an event that causes the JTree to
perform any necessary updates to the visible representation of the tree. In
init( ), the model is captured by calling getModel( ). When the button is
pressed, a new “branch” is created. Then the currently selected
component is found (or the root is used if nothing is selected) and the
model’s insertNodeInto( ) method does all the work of changing the
tree and causing it to be updated.
An example like the one above may give you what you need in a tree.
However, trees have the power to do just about anything you can
imagine—everywhere you see the word “default” in the example above,
you can substitute your own class to get different behavior. But beware:
almost all of these classes have a large interface, so you could spend a lot
of time struggling to understand the intricacies of trees. Despite this, it’s a
good design and the alternatives are usually much worse.
Tables
Like trees, tables in Swing are vast and powerful. They are primarily
intended to be the popular “grid” interface to databases via Java Database
Connectivity (JDBC, discussed in Chapter 15) and thus they have a
tremendous amount of flexibility, which you pay for in complexity.
There’s easily enough here to be the basis of a full-blown spreadsheet and
could probably justify an entire book. However, it is also possible to create
a relatively simple JTable if you understand the basics.
Actually, if you want to use the cross-platform (“metal”) look and feel that
is characteristic of Swing programs, you don’t have to do anything—it’s
the default. But if you want instead to use the current operating
environment’s look and feel, you just insert the following code, typically at
the beginning of your main( ) but somehow before any components are
added:
try {
UIManager.setLookAndFeel(UIManager.
getSystemLookAndFeelClassName());
} catch(Exception e) {}
You don’t need anything in the catch clause because the UIManager
will default to the cross-platform look and feel if your attempts to set up
any of the alternatives fail. However, during debugging the exception can
be quite useful so you may at least want to put a print statement in the
catch clause.
It is also possible to create a custom look and feel package, for example, if
you are building a framework for a company that wants a distinctive
appearance. This is a big job and is far beyond the scope of this book (in
fact, you’ll discover it is beyond the scope of many dedicated Swing
books!).
All the action takes place in the listeners. The CopyL and CutL listeners
are the same except for the last line of CutL, which erases the line that’s
been copied. The special two lines are the creation of a StringSelection
object from the String and the call to setContents( ) with this
StringSelection. That’s all there is to putting a String on the clipboard.
JAR files solve the problem by compressing all of your .class files into a
single file that is downloaded by the browser. Now you can create the
right design without worrying about how many .class files it will
generate, and the user will get a much faster download time.
Now you can create an HTML page with the new archive tag to indicate
the name of the JAR file. Here is the tag using the old form of the HTML
tag, as an illustration:
<head><title>TicTacToe Example Applet
</title></head>
<body>
<applet code=TicTacToe.class
archive=TicTacToe.jar
width=200 height=100>
</applet>
</body>
You’ll need to put it into the new (messy, complicated) form shown earlier
in the chapter in order to get it to work.
You should notice that event listeners are not guaranteed to be called in
the order they are added (although most implementations do in fact work
that way).
class BusinessLogic {
private int modifier;
public BusinessLogic(int mod) {
modifier = mod;
}
public void setModifier(int mod) {
modifier = mod;
}
public int getModifier() {
return modifier;
}
// Some business operations:
public int calculation1(int arg) {
return arg * modifier;
}
public int calculation2(int arg) {
return arg + modifier;
}
}
A canonical form
Inner classes, the Swing event model, and the fact that the old event
model is still supported along with new library features that rely on old-
style programming has added a new element of confusion to the code
design process. Now there are even more different ways for people to
write unpleasant code.
Except in extenuating circumstances you can always use the simplest and
clearest approach: listener classes (typically written as inner classes) to
solve your event-handling needs. This is the form used in most of the
examples in this chapter.
Visual programming
and Beans
So far in this book you’ve seen how valuable Java is for creating reusable
pieces of code. The “most reusable” unit of code has been the class, since
it comprises a cohesive unit of characteristics (fields) and behaviors
(methods) that can be reused either directly via composition or through
inheritance.
By now you’re probably used to the idea that an object is more than
characteristics; it’s also a set of behaviors. At design-time, the behaviors
of a visual component are partially represented by events, meaning
“Here’s something that can happen to the component.” Ordinarily, you
decide what you want to happen when an event occurs by tying code to
that event.
Here’s the critical part: the application builder tool uses reflection to
dynamically interrogate the component and find out which properties and
events the component supports. Once it knows what they are, it can
display the properties and allow you to change those (saving the state
when you build the program), and also display the events. In general, you
do something like double-clicking on an event and the application builder
tool creates a code body and ties it to that particular event. All you have to
do at that point is write the code that executes when the event occurs.
All this adds up to a lot of work that’s done for you by the application
builder tool. As a result you can focus on what the program looks like and
what it is supposed to do, and rely on the application builder tool to
manage the connection details for you. The reason that visual
programming tools have been so successful is that they dramatically
speed up the process of building an application—certainly the user
interface, but often other portions of the application as well.
What is a Bean?
After the dust settles, then, a component is really just a block of code,
typically embodied in a class. The key issue is the ability for the
application builder tool to discover the properties and events for that
component. To create a VB component, the programmer had to write a
fairly complicated piece of code following certain conventions to expose
the properties and events. Delphi was a second-generation visual
programming tool and the language was actively designed around visual
2. For a boolean property, you can use the “get” and “set” approach
above, but you can also use “is” instead of “get.”
4. For events, you use the Swing “listener” approach. It’s exactly the
same as you’ve been seeing:
addFooBarListener(FooBarListener) and
removeFooBarListener(FooBarListener) to handle a
FooBarEvent. Most of the time the built-in events and listeners
will satisfy your needs, but you can also create your own events and
listener interfaces.
Point 1 above answers a question about something you might have noticed
when looking at older code vs. newer code: a number of method names
have had small, apparently meaningless name changes. Now you can see
that most of those changes had to do with adapting to the “get” and “set”
class Spots {}
The events this Bean handles are ActionEvent and KeyEvent, based on
the naming of the “add” and “remove” methods for the associated listener.
Finally, you can see that the ordinary method croak( ) is still part of the
Bean simply because it’s a public method, not because it conforms to any
naming scheme.
Extracting BeanInfo
with the Introspector
One of the most critical parts of the Bean scheme occurs when you drag a
Bean off a palette and plop it onto a form. The application builder tool
must be able to create the Bean (which it can do if there’s a default
constructor) and then, without access to the Bean’s source code, extract
all the necessary information to create the property sheet and event
handlers.
Part of the solution is already evident from the end of Chapter 12: Java
reflection allows all the methods of an anonymous class to be discovered.
This is perfect for solving the Bean problem without requiring you to use
any extra language keywords like those required in other visual
Usually you won’t care about any of this—you’ll probably get most of your
Beans off the shelf from vendors, and you don’t need to know all the
magic that’s going on underneath. You’ll simply drag your Beans onto
your form, then configure their properties and write handlers for the
events you’re interested in. However, it’s an interesting and educational
exercise to use the Introspector to display information about a Bean, so
here’s a tool that does it:
//: c13:BeanDumper.java
// Introspecting a Bean.
// <applet code=BeanDumper width=600 height=500>
// </applet>
import java.beans.*;
import java.lang.reflect.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;
The public method list includes the methods that are not associated with
a property or event, such as croak( ), as well as those that are. These are
all the methods that you can call programmatically for a Bean, and the
application builder tool can choose to list all of these while you’re making
method calls, to ease your task.
Finally, you can see that the events are fully parsed out into the listener,
its methods, and the add- and remove-listener methods. Basically, once
you have the BeanInfo, you can find out everything of importance for
the Bean. You can also call the methods for that Bean, even though you
don’t have any other information except the object (again, a feature of
reflection).
The properties you can change are the size of the circle as well as the
color, size, and text of the word that is displayed when you press the
mouse. A BangBean also has its own addActionListener( ) and
You can see that all the fields are private, which is what you’ll usually do
with a Bean—allow access only through methods, usually using the
“property” scheme.
When you click the mouse, the text is put in the middle of the BangBean,
and if the actionListener field is not null, its actionPerformed( ) is
called, creating a new ActionEvent object in the process. Whenever the
mouse is moved, its new coordinates are captured and the canvas is
repainted (erasing any text that’s on the canvas, as you’ll see).
Here is the BangBeanTest class to allow you to test the bean as either an
applet or an application:
//: c13:BangBeanTest.java
// <applet code=BangBeanTest
Packaging a Bean
Before you can bring a Bean into a Bean-enabled visual builder tool, it
must be put into the standard Bean container, which is a JAR file that
includes all the Bean classes as well as a “manifest” file that says “This is a
Bean.” A manifest file is simply a text file that follows a particular form.
For the BangBean, the manifest file looks like this (without the first and
last lines):
//:! :BangBean.mf
Manifest-Version: 1.0
Name: bangbean/BangBean.class
Java-Bean: True
///:~
The first line indicates the version of the manifest scheme, which until
further notice from Sun is 1.0. The second line (empty lines are ignored)
names the BangBean.class file, and the third says, “It’s a Bean.”
Without the third line, the program builder tool will not recognize the
class as a Bean.
The only tricky part is that you must make sure that you get the proper
path in the “Name:” field. If you look back at BangBean.java, you’ll see
it’s in package bangbean (and thus in a subdirectory called “bangbean”
that’s off of the classpath), and the name in the manifest file must include
this package information. In addition, you must place the manifest file in
the directory above the root of your package path, which in this case
means placing the file in the directory above the “bangbean” subdirectory.
Then you must invoke jar from the same directory as the manifest file, as
follows:
jar cfm BangBean.jar BangBean.mf bangbean
You might wonder “What about all the other classes that were generated
when I compiled BangBean.java?” Well, they all ended up inside the
bangbean subdirectory, and you’ll see that the last argument for the
above jar command line is the bangbean subdirectory. When you give
jar the name of a subdirectory, it packages that entire subdirectory into
the jar file (including, in this case, the original BangBean.java source-
code file—you might not choose to include the source with your own
Beans). In addition, if you turn around and unpack the JAR file you’ve
just created, you’ll discover that your manifest file isn’t inside, but that jar
has created its own manifest file (based partly on yours) called
MANIFEST.MF and placed it inside the subdirectory META-INF (for
“meta-information”). If you open this manifest file you’ll also notice that
digital signature information has been added by jar for each file, of the
form:
Digest-Algorithms: SHA MD5
SHA-Digest: pDpEAG9NaeCx8aFtqPI4udSX/O0=
MD5-Digest: O4NcS1hE3Smnzlp2hj6qeg==
In general, you don’t need to worry about any of this, and if you make
changes you can just modify your original manifest file and reinvoke jar
to create a new JAR file for your Bean. You can also add other Beans to
the JAR file simply by adding their information to your manifest.
One thing to notice is that you’ll probably want to put each Bean in its
own subdirectory, since when you create a JAR file you hand the jar
utility the name of a subdirectory and it puts everything in that
subdirectory into the JAR file. You can see that both Frog and
BangBean are in their own subdirectories.
Once you have your Bean properly inside a JAR file you can bring it into a
Beans-enabled program-builder environment. The way you do this varies
from one tool to the next, but Sun provides a freely available test bed for
JavaBeans in their “Beans Development Kit” (BDK) called the “beanbox.”
(Download the BDK from java.sun.com/beans.) To place your Bean in the
One place where you can add sophistication is with properties. The
examples above have shown only single properties, but it’s also possible to
represent multiple properties in an array. This is called an indexed
property. You simply provide the appropriate methods (again following a
naming convention for the method names) and the Introspector
recognizes an indexed property so your application builder tool can
respond appropriately.
Properties can be bound, which means that they will notify other objects
via a PropertyChangeEvent. The other objects can then choose to
change themselves based on the change to the Bean.
Properties can be constrained, which means that other objects can veto a
change to that property if it is unacceptable. The other objects are notified
using a PropertyChangeEvent, and they can throw a
PropertyVetoException to prevent the change from happening and to
restore the old values.
You can also change the way your Bean is represented at design time:
1. You can provide a custom property sheet for your particular Bean.
The ordinary property sheet will be used for all other Beans, but
yours is automatically invoked when your Bean is selected.
More to Beans
There’s another issue that couldn’t be addressed here. Whenever you
create a Bean, you should expect that it will be run in a multithreaded
environment. This means that you must understand the issues of
threading, which will be introduced in Chapter 14. You’ll find a section
there called “JavaBeans revisited” that will look at the problem and its
solution.
Summary
Of all the libraries in Java, the GUI library has seen the most dramatic
changes from Java 1.0 to Java 2. The Java 1.0 AWT was roundly criticized
as being one of the worst designs seen, and while it would allow you to
create portable programs, the resulting GUI was “equally mediocre on all
platforms.” It was also limiting, awkward, and unpleasant to use
compared with the native application development tools available on a
particular platform.
When Java 1.1 introduced the new event model and JavaBeans, the stage
was set—now it was possible to create GUI components that could be
easily dragged and dropped inside visual application builder tools. In
addition, the design of the event model and Beans clearly shows strong
consideration for ease of programming and maintainable code (something
that was not evident in the 1.0 AWT). But it wasn’t until the JFC/Swing
classes appeared that the job was finished. With the Swing components,
cross-platform GUI programming can be a civilized experience.
This chapter was meant only to give you an introduction to the power of
Swing and to get you started so you could see how relatively simple it is to
feel your way through the libraries. What you’ve seen so far will probably
suffice for a good portion of your UI design needs. However, there’s a lot
more to Swing—it’s intended to be a fully powered UI design tool kit.
There’s probably a way to accomplish just about everything you can
imagine.
If you don’t see what you need here, delve into the online documentation
from Sun and search the Web, and if that’s not enough then find a
dedicated Swing book—a good place to start is The JFC Swing Tutorial,
by Walrath & Campione (Addison Wesley, 1999).
Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in Java
Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
10. Create your own JavaBean called Valve that contains two
properties: a boolean called “on” and an int called “level.” Create
a manifest file, use jar to package your Bean, then load it into the
13. Inherit a new type of button from JButton. Each time you press
this button, it should change its color to a randomly-selected
value. See ColorBoxes.java in Chapter 14 for an example of how
to generate a random color value.
18. Remember the “sketching box” toy with two knobs, one that
controls the vertical movement of the drawing point, and one that
controls the horizontal movement? Create one of those, using
SineWave.java to get you started. Instead of knobs, use sliders.
Add a button that will erase the entire sketch.
There are many possible uses for multithreading, but in general, you’ll
have some part of your program tied to a particular event or resource, and
you don’t want to hang up the rest of your program because of that. So
you create a thread associated with that event or resource and let it run
independently of the main program. A good example is a “quit” button—
you don’t want to be forced to poll the quit button in every piece of code
you write in your program and yet you want the quit button to be
responsive, as if you were checking it regularly. In fact, one of the most
immediately compelling reasons for multithreading is to produce a
responsive user interface.
825
Responsive user interfaces
As a starting point, consider a program that performs some CPU-intensive
operation and thus ends up ignoring user input and being unresponsive.
This one, a combined applet/application, will simply display the result of
a running counter:
//: c14:Counter1.java
// A non-responsive user interface.
// <applet code=Counter1 width=300 height=100>
// </applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;
Part of the infinite loop inside go( ) is to call sleep( ). sleep( ) must be
associated with a Thread object, and it turns out that every application
has some thread associated with it. (Indeed, Java is based on threads and
there are always some running along with your application.) So regardless
of whether you’re explicitly using threads, you can produce the current
thread used by your program with Thread and the static sleep( )
method.
The basic problem here is that go( ) needs to continue performing its
operations, and at the same time it needs to return so that
actionPerformed( ) can complete and the user interface can continue
responding to the user. But in a conventional method like go( ) it cannot
continue and at the same time return control to the rest of the program.
This sounds like an impossible thing to accomplish, as if the CPU must be
in two places at once, but this is precisely the illusion that threading
provides.
The following example creates any number of threads that it keeps track
of by assigning each thread a unique number, generated with a static
variable. The Thread’s run( ) method is overridden to count down each
time it passes through its loop and to finish when the count is zero (at the
point when run( ) returns, the thread is terminated).
//: c14:SimpleThread.java
// Very simple Threading example.
The output for one run of this program (it will be different from one run
to another) is:
Making 1
Making 2
Making 3
Making 4
Making 5
Thread 1(5)
Thread 1(4)
Thread 1(3)
Thread 1(2)
Thread 2(5)
Thread 2(4)
Thread 2(3)
Thread 2(2)
Thread 2(1)
Thread 1(1)
All Threads Started
Thread 3(5)
Thread 4(5)
Thread 4(4)
Thread 4(3)
Thread 4(2)
Thread 4(1)
Thread 5(5)
Thread 5(4)
Thread 5(3)
Thread 5(2)
Thread 5(1)
Thread 3(4)
Thread 3(3)
Thread 3(2)
Thread 3(1)
You can also see that the threads are not run in the order that they’re
created. In fact, the order that the CPU attends to an existing set of
threads is indeterminate, unless you go in and adjust the priorities using
Thread’s setPriority( ) method.
When you press the onOff button it toggles the runFlag inside the
SeparateSubTask object. That thread (when it looks at the flag) can
then start and stop itself. Pressing the onOff button produces an
apparently instant response. Of course, the response isn’t really instant,
not like that of a system that’s driven by interrupts. The counter stops
only when the thread has the CPU and notices that the flag has changed.
You can see that the inner class SeparateSubTask is private, which
means that its fields and methods can be given default access (except for
run( ), which must be public since it is public in the base class). The
private inner class is not accessible to anyone but Counter2, and the
two classes are tightly coupled. Anytime you notice classes that appear to
have high coupling with each other, consider the coding and maintenance
improvements you might get by using inner classes.
The following example repeats the form of the examples above with
counters and toggle buttons. But now all the information for a particular
counter, including the button and text field, is inside its own object that is
inherited from Thread. All the fields in Ticker are private, which
means that the Ticker implementation can be changed at will, including
the quantity and type of data components to acquire and display
1 Runnable was in Java 1.0, while inner classes were not introduced until Java 1.1, which
may partially account for the existence of Runnable. Also, traditional multithreading
architectures focused on a function to be run rather than an object. My preference is
always to inherit from Thread if I can; it seems cleaner and more flexible to me.
You’ll notice that the determination of the size of the array s is done
inside init( ), and not as part of an inline definition of s. That is, you
cannot say as part of the class definition (outside of any methods):
int size = Integer.parseInt(getParameter("size"));
Ticker[] s = new Ticker[size];
You can compile this, but you’ll get a strange “null-pointer exception” at
run-time. It works fine if you move the getParameter( ) initialization
inside of init( ). The applet framework performs the necessary startup to
grab the parameters before entering init( ).
Once the size of the array is established, new Ticker objects are created;
as part of the Ticker constructor the button and text field for each
Ticker is added to the applet.
Pressing the start button means looping through the entire array of
Tickers and calling start( ) for each one. Remember, start( ) performs
necessary thread initialization and then calls run( ) for that thread.
The ToggleL listener simply inverts the flag in Ticker and when the
associated thread next takes note it can react accordingly.
Daemon threads
A “daemon” thread is one that is supposed to provide a general service in
the background as long as the program is running, but is not part of the
essence of the program. Thus, when all of the non-daemon threads
complete, the program is terminated. Conversely, if there are any non-
daemon threads still running, the program doesn’t terminate. (There is,
for instance, a thread that runs main( ).)
You can find out if a thread is a daemon by calling isDaemon( ), and you
can turn the “daemonhood” of a thread on and off with setDaemon( ). If
a thread is a daemon, then any threads it creates will automatically be
daemons.
With multithreading, things aren’t lonely anymore, but you now have the
possibility of two or more threads trying to use the same limited resource
at once. Colliding over a resource must be prevented or else you’ll have
two threads trying to access the same bank account at the same time,
print to the same printer, or adjust the same valve, etc.
The Watcher class is a thread whose job is to call synchTest( ) for all of
the TwoCounter objects that are active. It does this by stepping through
the array that’s kept in the Sharing1 object. You can think of the
Watcher as constantly peeking over the shoulders of the TwoCounter
objects.
Note that to run this as an applet in a browser, your applet tag will need to
contain the lines:
Sometimes you don’t care if a resource is being accessed at the same time
you’re trying to use it (the food is on some other plate). But for
multithreading to work, you need some way to prevent two threads from
accessing the same resource, at least during critical periods.
There’s also a single lock per class (as part of the Class object for the
class), so that synchronized static methods can lock each other out
from simultaneous access of static data on a class-wide basis.
Note that if you want to guard some other resource from simultaneous
access by multiple threads, you can do so by forcing access to that
resource through synchronized methods.
Now a new issue arises. The Watcher can never get a peek at what’s
going on because the entire run( ) method has been synchronized, and
since run( ) is always running for each object the lock is always tied up
and synchTest( ) can never be called. You can see this because the
accessCount never changes.
What we’d like for this example is a way to isolate only part of the code
inside run( ). The section of code you want to isolate this way is called a
critical section and you use the synchronized keyword in a different
way to set up a critical section. Java supports critical sections with the
synchronized block; this time synchronized is used to specify the object
whose lock is being used to synchronize the enclosed code:
synchronized(syncObject) {
// This code can be accessed
// by only one thread at a time
}
Before the synchronized block can be entered, the lock must be acquired
on syncObject. If some other thread already has this lock, then the block
cannot be entered until the lock is given up.
Synchronized efficiency
Since having two methods write to the same piece of data never sounds
like a particularly good idea, it might seem to make sense for all methods
to be automatically synchronized and eliminate the synchronized
keyword altogether. (Of course, the example with a synchronized
run( ) shows that this wouldn’t work either.) But it turns out that
acquiring a lock is not a cheap operation—it multiplies the cost of a
method call (that is, entering and exiting from the method, not executing
the body of the method) by a minimum of four times, and could be much
more depending on your implementation. So if you know that a particular
method will not cause contention problems it is expedient to leave off the
synchronized keyword. On the other hand, leaving off the
synchronized keyword because you think it is a performance
bottleneck, and hoping that there aren’t any collisions is an invitation to
disaster.
The first point is fairly easy to deal with, but the second point requires a
little more thought. Consider the BangBean.java example presented in
the last chapter. That ducked out of the multithreading question by
ignoring the synchronized keyword (which hadn’t been introduced yet)
and making the event unicast. Here’s that example modified to work in a
multithreaded environment and to use multicasting for events:
//: c14:BangBean2.java
// You should write your Beans this way so they
// can run in a multithreaded environment.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
1. Does the method modify the state of “critical” variables within the
object? To discover whether the variables are “critical” you must
determine whether they will be read or set by other threads in the
program. (In this case, the reading or setting is virtually always
accomplished via synchronized methods, so you can just
examine those.) In the case of paint( ), no modification takes
place.
The test code in TestBangBean2 has been modified from that in the
previous chapter to demonstrate the multicast ability of BangBean2 by
adding extra listeners.
Blocking
A thread can be in any one of four states:
1. New: The thread object has been created but it hasn’t been started
yet so it cannot run.
2. Runnable: This means that a thread can be run when the time-
slicing mechanism has CPU cycles available for the thread. Thus,
the thread might or might not be running, but there’s nothing to
prevent it from being run if the scheduler can arrange it; it’s not
dead or blocked.
3. Dead: The normal way for a thread to die is by returning from its
run( ) method. You can also call stop( ), but this throws an
exception that’s a subclass of Error (which means you aren’t
forced to put the call in a try block). Remember that throwing an
exception should be a special event and not part of normal program
execution; thus the use of stop( ) is deprecated in Java 2. There’s
also a destroy( ) method (which has never been implemented)
that you should never call if you can avoid it since it’s drastic and
doesn’t release object locks.
You can also call yield( ) (a method of the Thread class) to voluntarily
give up the CPU so that other threads can run. However, the same thing
happens if the scheduler decides that your thread has had enough time
and jumps to another thread. That is, nothing prevents the scheduler
from moving your thread and giving time to some other thread. When a
thread is blocked, there’s some reason that it cannot continue running.
The following example shows all five ways of becoming blocked. It all
exists in a single file called Blocking.java, but it will be examined here
in discrete pieces. (You’ll notice the “Continued” and “Continuing” tags
that allow the code extraction tool to piece everything together.)
Sleeping
The first test in this program is with sleep( ):
///:Continuing
///////////// Blocking via sleep() ///////////
class Sleeper1 extends Blockable {
public Sleeper1(Container c) { super(c); }
public synchronized void run() {
You should be aware that Java 2 deprecates the use of suspend( ) and
resume( ), because suspend( ) holds the object’s lock and is thus
deadlock-prone. That is, you can easily get a number of locked objects
waiting on each other, and this will cause your program to freeze.
Although you might see them used in older programs you should not use
suspend( ) and resume( ). The proper solution is described later in this
chapter.
You’ll also see that there are two forms of wait( ). The first takes an
argument in milliseconds that has the same meaning as in sleep( ):
pause for this period of time. The difference is that in wait( ), the object
lock is released and you can come out of the wait( ) because of a
notify( ) as well as having the clock run out.
The second form takes no arguments, and means that the wait( ) will
continue until a notify( ) comes along and will not automatically
terminate after a time.
One fairly unique aspect of wait( ) and notify( ) is that both methods are
part of the base class Object and not part of Thread as are sleep( ),
suspend( ), and resume( ). Although this seems a bit strange at first—
to have something that’s exclusively for threading as part of the universal
base class—it’s essential because they manipulate the lock that’s also part
of every object. As a result, you can put a wait( ) inside any
synchronized method, regardless of whether there’s any threading
going on inside that particular class. In fact, the only place you can call
wait( ) is within a synchronized method or block. If you call wait( ) or
notify( ) within a method that’s not synchronized, the program will
compile, but when you run it you’ll get an
IllegalMonitorStateException with the somewhat nonintuitive
message “current thread not owner.” Note that sleep( ), suspend( ),
and resume( ) can all be called within non-synchronized methods
since they don’t manipulate the lock.
The Sender puts data into the Writer and sleeps for a random amount
of time. However, Receiver has no sleep( ), suspend( ), or wait( ).
But when it does a read( ) it automatically blocks when there is no more
data.
///:Continuing
class Sender extends Blockable { // send
private Writer out;
public Sender(Container c, Writer out) {
super(c);
this.out = out;
}
public void run() {
while(true) {
for(char c = 'A'; c <= 'z'; c++) {
try {
i++;
out.write(c);
state.setText("Sender sent: "
+ (char)c);
sleep((int)(3000 * Math.random()));
} catch(InterruptedException e) {
System.err.println("Interrupted");
} catch(IOException e) {
System.err.println("IO problem");
}
}
}
}
}
Testing
The main applet class is surprisingly simple because most of the work has
been put into the Blockable framework. Basically, an array of
Blockable objects is created, and since each one is a thread, they
perform their own activities when you press the “start” button. There’s
also a button and actionPerformed( ) clause to stop all of the Peeker
objects, which provides a demonstration of the alternative to the
deprecated (in Java 2) stop( ) method of Thread.
When the Blockable threads are initially created, each one automatically
creates and starts its own Peeker. So you’ll see the Peekers running
before the Blockable threads are started. This is important, as some of
the Peekers will get blocked and stop when the Blockable threads start,
and it’s essential to see this to understand that particular aspect of
blocking.
Deadlock
Because threads can become blocked and because objects can have
synchronized methods that prevent threads from accessing that object
until the synchronization lock is released, it’s possible for one thread to
get stuck waiting for another thread, which in turn waits for another
thread, etc., until the chain leads back to a thread waiting on the first one.
You get a continuous loop of threads waiting on each other and no one
can move. This is called deadlock. The claim is that it doesn’t happen that
often, but when it happens to you it’s frustrating to debug.
The destroy( ) method of Thread has never been implemented; it’s like
a suspend( ) that cannot resume, so it has the same deadlock issues as
suspend( ). However, this is not a deprecated method and it might be
implemented in a future version of Java (after 2) for special situations in
which the risk of a deadlock is acceptable.
You might wonder why these methods, now deprecated, were included in
Java in the first place. It seems an admission of a rather significant
mistake to simply remove them outright (and pokes yet another hole in
the arguments for Java’s exceptional design and infallibility touted by Sun
marketing people). The heartening part about the change is that it clearly
indicates that the technical people and not the marketing people are
running the show—they discovered a problem and they are fixing it. I find
this much more promising and hopeful than leaving the problem in
because “fixing it would admit an error.” It means that Java will continue
to improve, even if it means a little discomfort on the part of Java
programmers. I’d rather deal with the discomfort than watch the language
stagnate.
Priorities
The priority of a thread tells the scheduler how important this thread is.
If there are a number of threads blocked and waiting to be run, the
scheduler will run the one with the highest priority first. However, this
doesn’t mean that threads with lower priority don’t get run (that is, you
can’t get deadlocked because of priorities). Lower priority threads just
tend to run less often.
Also notice the use of yield( ), which voluntarily hands control back to
the scheduler. Without this the multithreading mechanism still works, but
you’ll notice it runs slowly (try removing the call to yield( ) to see this).
You could also call sleep( ), but then the rate of counting would be
controlled by the sleep( ) duration instead of the priority.
When you run this program, you’ll notice several things. First of all, the
thread group’s default priority is five. Even if you decrement the
maximum priority below five before starting the threads (or before
creating the threads, which requires a code change), each thread will have
a default priority of five.
The simple test is to take one counter and decrement its priority to one,
and observe that it counts much slower. But now try to increment it again.
You can get it back up to the thread group’s priority, but no higher. Now
decrement the thread group’s priority a couple of times. The thread
priorities are unchanged, but if you try to modify them either up or down
you’ll see that they’ll automatically pop to the priority of the thread group.
Also, new threads will still be given a default priority, even if that’s higher
than the group priority. (Thus the group priority is not a way to prevent
new threads from having higher priorities than existing ones.)
Thread groups
All threads belong to a thread group. This can be either the default thread
group or a group you explicitly specify when you create the thread. At
creation, the thread is bound to a group and cannot change to a different
group. Each application has at least one thread that belongs to the system
thread group. If you create more threads without specifying a group, they
will also belong to the system thread group.
Thread groups must also belong to other thread groups. The thread group
that a new one belongs to must be specified in the constructor. If you
create a thread group without specifying a thread group for it to belong to,
The reason for the existence of thread groups is hard to determine from
the literature, which tends to be confusing on this subject. It’s often cited
as “security reasons.” According to Arnold & Gosling,2 “Threads within a
thread group can modify the other threads in the group, including any
farther down the hierarchy. A thread cannot modify threads outside of its
own group or contained groups.” It’s hard to know what “modify” is
supposed to mean here. The following example shows a thread in a “leaf”
subgroup modifying the priorities of all the threads in its tree of thread
groups as well as calling a method for all the threads in its tree.
//: c14:TestAccess.java
// How threads can access other threads
// in a parent thread group.
2 The Java Programming Language, by Ken Arnold and James Gosling, Addison-Wesley
1996 pp 179.
The debugging method list( ) prints all the information about a thread
group to standard output and is helpful when investigating thread group
behavior. Here’s the output of the program:
java.lang.ThreadGroup[name=x,maxpri=10]
Thread[one,5,x]
java.lang.ThreadGroup[name=y,maxpri=10]
java.lang.ThreadGroup[name=z,maxpri=10]
Thread[two,5,z]
one f()
two f()
java.lang.ThreadGroup[name=x,maxpri=10]
Thread[one,1,x]
java.lang.ThreadGroup[name=y,maxpri=10]
java.lang.ThreadGroup[name=z,maxpri=10]
Thread[two,1,z]
Not only does list( ) print the class name of ThreadGroup or Thread,
but it also prints the thread group name and its maximum priority. For
threads, the thread name is printed, followed by the thread priority and
the group that it belongs to. Note that list( ) indents the threads and
thread groups to indicate that they are children of the unindented thread
group.
You can see that f( ) is called by the TestThread2 run( ) method, so it’s
obvious that all threads in a group are vulnerable. However, you can
access only the threads that branch off from your own system thread
group tree, and perhaps this is what is meant by “safety.” You cannot
access anyone else’s system thread group tree.
The second exercise shows that the system group’s maximum priority
can be reduced and the main thread can have its priority increased:
The fourth exercise reduces g1’s maximum priority by two and then tries
to increase it up to Thread.MAX_PRIORITY. The result is:
(4) ThreadGroup[name=g1,maxpri=8]
Thread[A,9,g1]
You can see that the increase in maximum priority didn’t work. You can
only decrease a thread group’s maximum priority, not increase it. Also,
notice that thread A’s priority didn’t change, and now it is higher than the
thread group’s maximum priority. Changing a thread group’s maximum
priority doesn’t affect existing threads.
The default thread priority for this program is six; that’s the priority a new
thread will be created at and where it will stay if you don’t manipulate the
priority. Exercise 6 lowers the maximum thread group priority below the
default thread priority to see what happens when you create a new thread
under this condition:
(6) ThreadGroup[name=g1,maxpri=3]
After all of these experiments, the entire system of thread groups and
threads is printed:
(10)ThreadGroup[name=system,maxpri=9]
Thread[main,6,system]
ThreadGroup[name=g1,maxpri=3]
Thread[A,9,g1]
Thread[B,8,g1]
Thread[C,3,g1]
ThreadGroup[name=g2,maxpri=3]
Thread[0,6,g2]
Thread[1,6,g2]
Thread[2,6,g2]
The last part of this program demonstrates methods for an entire group of
threads. First the program moves through the entire tree of threads and
starts each one that hasn’t been started. For drama, the system group is
then suspended and finally stopped. (Although it’s interesting to see that
suspend( ) and stop( ) work on entire thread groups, you should keep
in mind that these methods are deprecated in Java 2.) But when you
suspend the system group you also suspend the main thread and the
whole program shuts down, so it never gets to the point where the threads
are stopped. Actually, if you do stop the main thread it throws a
ThreadDeath exception, so this is not a typical thing to do. Since
ThreadGroup is inherited from Object, which contains the wait( )
method, you can also choose to suspend the program for any number of
seconds by calling wait(seconds * 1000). This must acquire the lock
inside a synchronized block, of course.
Thread groups can seem a bit mysterious at first, but keep in mind that
you probably won’t be using them directly very often.
Runnable revisited
Earlier in this chapter, I suggested that you think carefully before making
an applet or main Frame as an implementation of Runnable. Of course,
if you must inherit from a class and you want to add threading behavior to
the class, Runnable is the correct solution. The final example in this
chapter exploits this by making a Runnable JPanel class that paints
different colors on itself. This application is set up to take values from the
command line to determine how big the grid of colors is and how long to
CBox is where all the work takes place. This is inherited from JPanel
and it implements the Runnable interface so each JPanel can also be a
Thread. Remember that when you implement Runnable, you don’t
make a Thread object, just a class that has a run( ) method. Thus, you
must explicitly create a Thread object and hand the Runnable object to
the constructor, then call start( ) (this happens in the constructor). In
CBox this thread is called t.
Notice the array colors, which is an enumeration of all the colors in class
Color. This is used in newColor( ) to produce a randomly selected
color. The current cell color is cColor.
In run( ), you see the infinite loop that sets the cColor to a new random
color and then calls repaint( ) to show it. Then the thread goes to
sleep( ) for the amount of time specified on the command line.
class CBoxList
extends ArrayList implements Runnable {
private Thread t;
private int pause;
public CBoxList(int pause) {
this.pause = pause;
t = new Thread(this);
}
public void go() { t.start(); }
public void run() {
while(true) {
int i = (int)(Math.random() * size());
((CBox2)get(i)).nextColor();
try {
t.sleep(pause);
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
public Object last() { return get(size() - 1);}
}
The CBoxList could also have inherited Thread and had a member
object of type ArrayList. That design has the advantage that the add( )
and get( ) methods could then be given specific argument and return
value types instead of generic Objects. (Their names could also be
As before, when you implement Runnable you don’t get all of the
equipment that comes with Thread, so you have to create a new Thread
and hand yourself to its constructor in order to have something to
start( ), as you can see in the CBoxList constructor and in go( ). The
run( ) method simply chooses a random element number within the list
and calls nextColor( ) for that element to cause it to choose a new
randomly selected color.
Upon running this program, you see that it does indeed run faster and
respond more quickly (for instance, when you interrupt it, it stops more
quickly), and it doesn’t seem to bog down as much at higher grid sizes.
Thus, a new factor is added into the threading equation: you must watch
to see that you don’t have “too many threads” (whatever that turns out to
mean for your particular program and platform—here, the slowdown in
ColorBoxes appears to be caused by the fact that there’s only one thread
that is responsible for all painting, and it gets bogged down by too many
requests). If you have too many threads, you must try to use techniques
like the one above to “balance” the number of threads in your program. If
you see performance problems in a multithreaded program you now have
a number of issues to examine:
Issues like this are one reason that multithreaded programming is often
considered an art.
Threading is like stepping into an entirely new world and learning a whole
new programming language, or at least a new set of language concepts.
With the appearance of thread support in most microcomputer operating
systems, extensions for threads have also been appearing in programming
languages or libraries. In all cases, thread programming (1) seems
mysterious and requires a shift in the way you think about programming;
and (2) looks similar to thread support in other languages, so when you
understand threads, you understand a common tongue. And although
support for threads can make Java seem like a more complicated
language, don’t blame Java. Threads are tricky.
Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in Java
Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
3. Create two Thread subclasses, one with a run( ) that starts up,
captures the reference of the second Thread object and then calls
wait( ). The other class’ run( ) should call notifyAll( ) for the
first thread after some number of seconds have passed, so the first
thread can print a message.
10. Modify Exercise 9 so that multiple sine wave panels are created
within the application. The number of sine wave panels should be
controlled by HTML tags or command-line parameters.
! Get some information from that machine over there and move it to
this machine here, or vice versa. This is accomplished with basic
network programming.
903
! Use code written in other languages, running on other
architectures. This is accomplished using the Common Object
Request Broker Architecture (CORBA), which is directly
supported by Java.
Each topic will be given a light introduction in this chapter. Please note
that each subject is voluminous and by itself the subject of entire books,
so this chapter is only meant to familiarize you with the topics, not make
you an expert (however, you can go a long way with the information
presented here on network programming, servlets and JSPs).
Network programming
One of Java’s great strengths is painless networking. The Java network
library designers have made it quite similar to reading and writing files,
except that the “file” exists on a remote machine and the remote machine
can decide exactly what it wants to do about the information you’re
requesting or sending. As much as possible, the underlying details of
networking have been abstracted away and taken care of within the JVM
and local machine installation of Java. The programming model you use is
that of a file; in fact, you actually wrap the network connection (a
“socket”) with stream objects, so you end up using the same method calls
as you do with all other streams. In addition, Java’s built-in
multithreading is exceptionally handy when dealing with another
networking issue: handling multiple connections at once.
2. Alternatively, you can use the “dotted quad” form, which is four
numbers separated by dots, such as 123.255.28.120.
1 This means a maximum of just over four billion numbers, which is rapidly running out.
The new standard for IP addresses will use a 128-bit number, which should produce
enough unique IP addresses for the foreseeable future.
The machine that “stays in one place” is called the server, and the one
that seeks is called the client. This distinction is important only while the
client is trying to connect to the server. Once they’ve connected, it
becomes a two-way communication process and it doesn’t matter
anymore that one happened to take the role of server and the other
happened to take the role of the client.
So the job of the server is to listen for a connection, and that’s performed
by the special server object that you create. The job of the client is to try to
make a connection to a server, and this is performed by the special client
object you create. Once the connection is made, you’ll see that at both
server and client ends, the connection is magically turned into an I/O
stream object, and from then on you can treat the connection as if you
were reading from and writing to a file. Thus, after the connection is made
you will just use the familiar I/O commands from Chapter 11. This is one
of the nice features of Java networking.
You can also produce the local loopback address by handing it the string
localhost:
InetAddress.getByName("localhost");
(assuming “localhost” is configured in your machine’s “hosts” table), or by
using its dotted quad form to name the reserved IP number for the
loopback:
InetAddress.getByName("127.0.0.1");
All three forms produce the same result.
Sockets
The socket is the software abstraction used to represent the “terminals” of
a connection between two machines. For a given connection, there’s a
socket on each machine, and you can imagine a hypothetical “cable”
running between the two machines with each end of the “cable” plugged
into a socket. Of course, the physical hardware and cabling between
machines is completely unknown. The whole point of the abstraction is
that we don’t have to know more than is necessary.
In Java, you create a socket to make the connection to the other machine,
then you get an InputStream and OutputStream (or, with the
appropriate converters, Reader and Writer) from the socket in order to
be able to treat the connection as an I/O stream object. There are two
stream-based socket classes: a ServerSocket that a server uses to
“listen” for incoming connections and a Socket that a client uses in order
to initiate a connection. Once a client makes a socket connection, the
ServerSocket returns (via the accept( ) method) a corresponding
Socket through which communications will take place on the server side.
From then on, you have a true Socket to Socket connection and you
treat both ends the same way because they are the same. At this point,
you use the methods getInputStream( ) and getOutputStream( ) to
produce the corresponding InputStream and OutputStream objects
from each Socket. These must be wrapped inside buffers and formatting
classes just like any other stream object described in Chapter 11.
When you create a ServerSocket, you give it only a port number. You
don’t have to give it an IP address because it’s already on the machine it
represents. When you create a Socket, however, you must give both the
IP address and the port number where you’re trying to connect.
(However, the Socket that comes back from ServerSocket.accept( )
already contains all this information.)
The same logic is used for the Socket returned by accept( ). If accept( )
fails, then we must assume that the Socket doesn’t exist or hold any
resources, so it doesn’t need to be cleaned up. If it’s successful, however,
the following statements must be in a try-finally block so that if they fail
the Socket will still be cleaned up. Care is required here because sockets
use important nonmemory resources, so you must be diligent in order to
clean them up (since there is no destructor in Java to do it for you).
The next part of the program looks just like opening files for reading and
writing except that the InputStream and OutputStream are created
from the Socket object. Both the InputStream and OutputStream
objects are converted to Reader and Writer objects using the
“converter” classes InputStreamReader and OutputStreamWriter,
respectively. You could also have used the Java 1.0 InputStream and
OutputStream classes directly, but with output there’s a distinct
advantage to using the Writer approach. This appears with
PrintWriter, which has an overloaded constructor that takes a second
argument, a boolean flag that indicates whether to automatically flush
the output at the end of each println( ) (but not print( )) statement.
Every time you write to out, its buffer must be flushed so the information
goes out over the network. Flushing is important for this particular
example because the client and server each wait for a line from the other
party before proceeding. If flushing doesn’t occur, the information will not
be put onto the network until the buffer is full, which causes lots of
problems in this example.
Note that, like virtually all streams you open, these are buffered. There’s
an exercise at the end of this chapter to show you what happens if you
don’t buffer the streams (things get slow).
When the client sends the line consisting of “END,” the program breaks
out of the loop and closes the Socket.
Note that the Socket called socket is created with both the
InetAddress and the port number. To understand what it means when
you print one of these Socket objects, remember that an Internet
connection is determined uniquely by these four pieces of data:
clientHost, clientPortNumber, serverHost, and
serverPortNumber. When the server comes up, it takes up its assigned
port (8080) on the localhost (127.0.0.1). When the client comes up, it is
allocated to the next available port on its machine, 1077 in this case,
This means that the server just accepted a connection from 127.0.0.1 on
port 1077 while listening on its local port (8080). On the client side:
Socket[addr=localhost/127.0.0.1,PORT=8080,localport=1077]
which means that the client made a connection to 127.0.0.1 on port 8080
using the local port 1077.
You’ll notice that every time you start up the client anew, the local port
number is incremented. It starts at 1025 (one past the reserved block of
ports) and keeps going up until you reboot the machine, at which point it
starts at 1025 again. (On UNIX machines, once the upper limit of the
socket range is reached, the numbers will wrap around to the lowest
available number again.)
Once the Socket object has been created, the process of turning it into a
BufferedReader and PrintWriter is the same as in the server (again,
in both cases you start with a Socket). Here, the client initiates the
conversation by sending the string “howdy” followed by a number. Note
that the buffer must again be flushed (which happens automatically via
the second argument to the PrintWriter constructor). If the buffer isn’t
flushed, the whole conversation will hang because the initial “howdy” will
never get sent (the buffer isn’t full enough to cause the send to happen
automatically). Each line that is sent back from the server is written to
System.out to verify that everything is working correctly. To terminate
the conversation, the agreed-upon “END” is sent. If the client simply
hangs up, then the server throws an exception.
You can see that the same care is taken here to ensure that the network
resources represented by the Socket are properly cleaned up, using a
try-finally block.
The basic scheme is to make a single ServerSocket in the server and call
accept( ) to wait for a new connection. When accept( ) returns, you take
the resulting Socket and use it to create a new thread whose job is to
serve that particular client. Then you call accept( ) again to wait for a
new client.
In the following server code, you can see that it looks similar to the
JabberServer.java example except that all of the operations to serve a
particular client have been moved inside a separate thread class:
//: c15:MultiJabberServer.java
// A server that uses multithreading
// to handle any number of clients.
import java.io.*;
import java.net.*;
To test that the server really does handle multiple clients, the following
program creates many clients (using threads) that connect to the same
server. The maximum number of threads allowed is determined by the
final int MAX_THREADS.
//: c15:MultiJabberClient.java
// Client that tests the MultiJabberServer
// by starting up multiple clients.
import java.net.*;
import java.io.*;
Datagrams
The examples you’ve seen so far use the Transmission Control Protocol
(TCP, also known as stream-based sockets), which is designed for
ultimate reliability and guarantees that the data will get there. It allows
retransmission of lost data, it provides multiple paths through different
routers in case one goes down, and bytes are delivered in the order they
are sent. All this control and reliability comes at a cost: TCP has a high
overhead.
More to networking
There’s actually a lot more to networking than can be covered in this
introductory treatment. Java networking also provides fairly extensive
support for URLs, including protocol handlers for different types of
content that can be discovered at an Internet site. You can find other Java
networking features fully and carefully described in Java Network
Programming by Elliotte Rusty Harold (O’Reilly, 1997).
One of the major problems with databases has been the feature wars
between the database companies. There is a “standard” database
language, Structured Query Language (SQL-92), but you must usually
know which database vendor you’re working with despite the standard.
JDBC is designed to be platform-independent, so you don’t need to worry
about the database you’re using while you’re programming. However, it’s
still possible to make vendor-specific calls from JDBC so you aren’t
restricted from doing what you must.
One place where programmers may need to use SQL type names is in the
SQL TABLE CREATE statement when they are creating a new database
table and defining the SQL type for each column. Unfortunately there are
significant variations between SQL types supported by different database
products. Different databases that support SQL types with the same
semantics and structure may give those types different names. Most
major databases support an SQL data type for large binary values: in
Oracle this type is called a LONG RAW, Sybase calls it IMAGE, Informix
calls it BYTE, and DB2 calls it LONG VARCHAR FOR BIT DATA.
Therefore, if database portability is a goal you should try to use only
generic SQL type identifiers.
3. The database identifier. This varies with the database driver used,
but it generally provides a logical name that is mapped by the
database administration software to a physical directory where the
database tables are located. For your database identifier to have
any meaning, you must register the name using your database
administration software. (The process of registration varies from
platform to platform.)
All this information is combined into one string, the “database URL.” For
example, to connect through the ODBC subprotocol to a database
identified as “people,” the database URL could be:
String dbUrl = "jdbc:odbc:people";
If you’re connecting across a network, the database URL will contain the
connection information identifying the remote machine and can become a
bit intimidating. Here is an example of a CloudScape database being
called from a remote client utilizing RMI:
jdbc:rmi://192.168.170.27:1099/jdbc:cloudscape:db
When you’re ready to connect to the database, call the static method
DriverManager.getConnection( ) and pass it the database URL, the
user name, and a password to get into the database. You get back a
Connection object that you can then use to query and manipulate the
database.
Of course, this process can vary radically from machine to machine, but
the process I used to make it work under 32-bit Windows might give you
clues to help you attack your own situation.
If the load statement is wrong, you’ll get an exception at this point. To test
whether your driver load statement is working correctly, comment out the
code after the statement and up to the catch clause; if the program
throws no exceptions it means that the driver is loading properly.
First, open the control panel. You might find two icons that say “ODBC.”
You must use the one that says “32bit ODBC,” since the other one is for
backward compatibility with 16-bit ODBC software and will produce no
results for JDBC. When you open the “32bit ODBC” icon, you’ll see a
tabbed dialog with a number of tabs, including “User DSN,” “System
DSN,” “File DSN,” etc., in which “DSN” means “Data Source Name.” It
turns out that for the JDBC-ODBC bridge, the only place where it’s
important to set up your database is “System DSN,” but you’ll also want to
test your configuration and create queries, and for that you’ll also need to
set up your database in “File DSN.” This will allow the Microsoft Query
tool (that comes with Microsoft Office) to find the database. Note that
other query tools are also available from other vendors.
The most interesting database is one that you’re already using. Standard
ODBC supports a number of different file formats including such
venerable workhorses as DBase. However, it also includes the simple
“comma-separated ASCII” format, which virtually every data tool has the
ability to write. In my case, I just took my “people” database that I’ve been
maintaining for years using various contact-management tools and
exported it as a comma-separated ASCII file (these typically have an
extension of .csv). In the “System DSN” section I chose “Add,” chose the
text driver to handle my comma-separated ASCII file, and then un-
checked “use current directory” to allow me to specify the directory where
I exported the data file.
You’ll notice when you do this that you don’t actually specify a file, only a
directory. That’s because a database is typically represented as a collection
of files under a single directory (although it could be represented in other
forms as well). Each file usually contains a single table, and the SQL
statements can produce results that are culled from multiple tables in the
database (this is called a join). A database that contains only a single table
(like my “people” database) is usually called a flat-file database. Most
problems that go beyond the simple storage and retrieval of data generally
Once you’ve done this, you will see that your database is available when
you create a new query using your query tool.
1. Start a new query and use the Query Wizard. Select the “people”
database. (This is the equivalent of opening the database
connection using the appropriate database URL.)
The result of this query will show you whether you’re getting what you
want.
Now you can press the SQL button and without any research on your part,
up will pop the correct SQL code, ready for you to cut and paste. For this
query, it looked like this:
SELECT people.FIRST, people.LAST, people.EMAIL
FROM people.csv people
WHERE (people.LAST='Eckel') AND
(people.EMAIL Is Not Null)
ORDER BY people.FIRST
Especially with more complicated queries it’s easy to get things wrong, but
by using a query tool you can interactively test your queries and
automatically generate the correct code. It’s hard to argue the case for
doing this by hand.
You can see from this example that by using the tools currently available—
in particular the query-building tool—database programming with SQL
and JDBC can be quite straightforward.
MEMBERS
MEM_ID LOCATIONS
MEM_UNAME (AK) LOC_ID
MEM_LNAME (IE)
MEM_FNAME (IE) NAME (IE)
ADDRESS CONTACT
CITY ADDRESS
STATE CITY
ZIP STATE
PHONE ZIP
EMAIL PHONE
DIRECTIONS
In this example, it makes sense to let the exceptions be thrown out to the
console:
//: c15:jdbc:CIDCreateTables.java
// Creates database tables for the
// community interests database.
import java.sql.*;
To test the database, it is loaded with some sample data. This requires a
series of INSERTs followed by a SELECT to produce result set. To make
additions and changes to the test data easy, the test data is set up as a
two-dimensional array of Objects, and the executeInsert( ) method
can then use the information in one row of the table to create the
appropriate SQL command.
class TestSet {
Object[][] data = {
{ "MEMBERS", new Integer(1),
"dbartlett", "Bartlett", "David",
"123 Mockingbird Lane",
"Gettysburg", "PA", "19312",
"123.456.7890", "[email protected]" },
{ "MEMBERS", new Integer(2),
"beckel", "Eckel", "Bruce",
"123 Over Rainbow Lane",
"Crested Butte", "CO", "81224",
"123.456.7890", "[email protected]" },
{ "MEMBERS", new Integer(3),
"rcastaneda", "Castaneda", "Robert",
"123 Downunder Lane",
"Sydney", "NSW", "12345",
"123.456.7890", "[email protected]" },
{ "LOCATIONS", new Integer(1),
"Center for Arts",
"Betty Wright", "123 Elk Ave.",
"Crested Butte", "CO", "81224",
"123.456.7890",
"Go this way then that." },
{ "LOCATIONS", new Integer(2),
"Witts End Conference Center",
"John Wittig", "123 Music Drive",
"Zoneville", "PA", "19123",
"123.456.7890",
"Go that way then this." },
{ "EVENTS", new Integer(1),
"Project Management Myths",
"Software Development",
new Integer(1), new Float(2.50),
"2000-07-17 19:30:00" },
{ "EVENTS", new Integer(2),
"Life of the Crested Dog",
"Archeology",
The constructor for LoadDB makes the connection, and load( ) steps
through the data and calls executeInsert( ) for each record. cleanup( )
closes the statement and the connection; to guarantee that this is called, it
is placed inside a finally clause.
3 Dave Bartlett was instrumental in the development of this material, and also the JSP
section.
The most convenient attribute of the servlet API is the auxiliary objects
that come along with the HttpServlet class to support it. If you look at the
service( ) method in the Servlet interface, you’ll see it has two
parameters: ServletRequest and ServletResponse. With the
HttpServlet class these two object are extended for HTTP:
HttpServletRequest and HttpServletResponse. Here’s a simple
example that shows the use of HttpServletResponse:
//: c15:servlets:ServletsRule.java
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
After setting the content type of the response (which must always be done
before the Writer or OutputStream is procured), the getWriter( )
method of the response object produces a PrintWriter object, which is
used for writing character-based response data (alternatively,
getOutputStream( ) produces an OutputStream, used for binary
response, which is only utilized in more specialized solutions).
The rest of the program simply sends HTML back to the client (it’s
assumed you understand HTML, so that part is not explained) as a
sequence of Strings. However, notice the inclusion of the “hit counter”
represented by the variable i. This is automatically converted to a String
in the print( ) statement.
When you run the program, you’ll notice that the value of i is retained
between requests to the servlet. This is an essential property of servlets:
since only one servlet of a particular class is loaded into the container, and
it is never unloaded (unless the servlet container is terminated, which is
something that only normally happens if you reboot the server computer),
any fields of that servlet class effectively become persistent objects! This
means that you can effortlessly maintain values between servlet requests,
whereas with CGI you had to write values to disk in order to preserve
them, which required a fair amount of fooling around to get it right, and
resulted in a non-cross-platform solution.
Of course, sometimes the Web server, and thus the servlet container,
must be rebooted as part of maintenance or during a power failure. To
avoid losing any persistent information, the servlet’s init( ) and
destroy( ) methods are automatically called whenever the servlet is
loaded or unloaded, giving you the opportunity to save data during
shutdown, and restore it after rebooting. The servlet container calls the
destroy( ) method as it is terminating itself, so you always get an
opportunity to save valuable data as long as the server machine is
configured in an intelligent way.
There are several methods of session tracking, but the most common
method is with persistent “cookies,” which are an integral part of the
Internet standards. The HTTP Working Group of the Internet
Engineering Task Force has written cookies into the official standard in
RFC 2109 (ds.internic.net/rfc/rfc2109.txt or check
www.cookiecentral.com).
The Session class of the servlet API uses the Cookie class to do its work.
However, all the Session object needs is some kind of unique identifier
stored on the client and passed to the server. Web sites may also use the
other types of session tracking but these mechanisms will be more
difficult to implement as they are not encapsulated into the servlet API
(that is, you must write them by hand to deal with the situation when the
client has disabled cookies).
The Session object, if it is not new, will give us details about the client
from previous visits. If the Session object is new then the program will
start to gather information about this client’s activities on this visit.
Capturing this client information is done through the setAttribute( )
and getAttribute( ) methods of the session object.
java.lang.Object getAttribute(java.lang.String)
void setAttribute(java.lang.String name,
java.lang.Object value)
The Session object uses a simple name-value pairing for loading
information. The name is a String, and the value can be any object
derived from java.lang.Object. SessionPeek keeps track of how many
times the client has been back during this session. This is done with an
Integer object named sesspeek.cntr. If the name is not found an
Integer is created with value of one, otherwise an Integer is created
with the incremented value of the previously held Integer. The new
Integer is placed into the Session object. If you use same key in a
setAttribute( ) call, then the new object overwrites the old one. The
incremented counter is used to display the number of times that the client
has visited during this session.
You may wonder how long a Session object hangs around. The answer
depends on the servlet container you are using; they usually default to 30
minutes (1800 seconds), which is what you should see from the
ServletPeek call to getMaxInactiveInterval( ). Tests seem to
produce mixed results between servlet containers. Sometimes the
Session object can hang around overnight, but I have never seen a case
where the Session object disappears in less than the time specified by the
This has only been a brief introduction to servlets; there are entire books
on the subject. However, this introduction should give you enough ideas
to get you started. In addition, many of the ideas in the next section are
backward compatible with servlets.
JSPs allow you to combine the HTML of a Web page with pieces of Java
code in the same document. The Java code is surrounded by special tags
that tell the JSP container that it should use the code to generate a servlet,
or part of one. The benefit of JSPs is that you can maintain a single
document that represents both the page and the Java code that enables it.
The downside is that the maintainer of the JSP page must be skilled in
both HTML and Java (however, GUI builder environments for JSPs
should be forthcoming).
From then on, as long as the JSP source for the page is not modified, it
behaves as if it were a static HTML page with associated servlets (all the
HTML code is actually generated by the servlet, however). If you modify
the source code for the JSP, it is automatically recompiled and reloaded
the next time that page is requested. Of course, because of all this
dynamism you’ll see a slow response for the first-time access to a JSP.
However, since a JSP is usually used much more than it is changed, you
will normally not be affected by this delay.
Here’s an extremely simple JSP example that uses a standard Java library
call to get the current time in milliseconds, which is then divided by 1000
to produce the time in seconds. Since a JSP expression (the <%= ) is
used, the result of the calculation is coerced into a String and placed on
the generated Web page:
//:! c15:jsp:ShowSeconds.jsp
<html><body>
<H1>The time in seconds is:
<%= System.currentTimeMillis()/1000 %></H1>
</body></html>
///:~
In the JSP examples in this book, the first and last lines are not included
in the actual code file that is extracted and placed in the book’s source-
code tree.
Implicit objects
Servlets include classes that provide convenient utilities, such as
HttpServletRequest, HttpServletResponse, Session, etc. Objects
of these classes are built into the JSP specification and automatically
available for use in your JSP without writing any extra lines of code. The
implicit objects in a JSP are detailed in the table below.
The scope of each object can vary significantly. For example, the session
object has a scope which exceeds that of a page, as it many span several
client requests and pages. The application object can provide services to
a group of JSP pages that together represent a Web application.
JSP directives
Directives are messages to the JSP container and are denoted by the “@”:
<%@ directive {attr="value"}* %>
Directives do not send anything to the out stream, but they are important
in setting up your JSP page’s attributes and dependencies with the JSP
container. For example, the line:
<%@ page language="java" %>
says that the scripting language being used within the JSP page is Java. In
fact, the JSP specification only describes the semantics of scripts for the
language attribute equal to “Java.” The intent of this directive is to build
flexibility into the JSP technology. In the future, if you were to choose
another language, say Python (a good scripting choice), then that
language would have to support the Java Run-time Environment by
exposing the Java technology object model to the scripting environment,
especially the implicit variables defined above, JavaBeans properties, and
public methods.
The import attribute describes the types that are available to the
scripting environment. This attribute is used just as it would be in the
Java programming language, i.e., a comma-separated list of ordinary
import expressions. This list is imported by the translated JSP page
implementation and is available to the scripting environment. Again, this
is currently only defined when the value of the language directive is
“java.”
All these tags are based upon XML; you could even say that a JSP page
can be mapped to a XML document. The XML equivalent syntax for the
scripting elements above would be:
<jsp:declaration> declaration </jsp:declaration>
<jsp:scriptlet> scriptlet </jsp:scriptlet>
At the end of the example is a scriptlet that writes “Goodbye” to the Web
server console and “Cheerio” to the implicit JspWriter object out.
Scriptlets can contain any code fragments that are valid Java statements.
Scriptlets are executed at request-processing time. When all the scriptlet
fragments in a given JSP are combined in the order they appear in the JSP
page, they should yield a valid statement as defined by the Java
programming language. Whether or not they produce any output into the
out stream depends upon the code in the scriptlet. You should be aware
that scriptlets can produce side effects by modifying the objects that are
visible to them.
JSP expressions can found intermingled with the HTML in the middle
section of Hello.jsp. Expressions must be complete Java statements,
which are evaluated, coerced to a String, and sent to out. If the result of
the expression cannot be coerced to a String then a
ClassCastException is thrown.
The first piece of information produced is the name of the servlet, which
will probably just be “JSP” but it depends on your implementation. You
can also discover the current version of the servlet container by using the
application object. Finally, after setting a session attribute, the “attribute
names” in a particular scope are displayed. You don’t use the scopes very
much in most JSP programming; they were just shown here to add
When you first bring up this session you will see a MaxInactiveInterval
of, for example, 1800 seconds (30 minutes). This will depend on the way
your JSP/servlet container is configured. The MaxInactiveInterval is
shortened to 5 seconds to make things interesting. If you refresh the page
before the 5 second interval expires, then you’ll see:
Session value for "My dog" = Ralph
But if you wait longer than that, “Ralph” will become null.
To see how the session information can be carried through to other pages,
and also to see the effect of invalidating a session object versus just letting
it expire, two other JSPs are created. The first one (reached by pressing
the “invalidate” button in SessionObject.jsp) reads the session
information and then explicitly invalidates that session:
//:! c15:jsp:SessionObject2.jsp
<%--The session object carries through--%>
<html><body>
<H1>Session id: <%= session.getId() %></H1>
<H1>Session value for "My dog"
<%= session.getValue("My dog") %></H1>
<% session.invalidate(); %>
</body></html>
///:~
To experiment with this, refresh SessionObject.jsp, then immediately
click the “invalidate” button to bring you to SessionObject2.jsp. At this
After displaying the session identifier, each cookie in the array of cookies
that comes in with the request object is displayed, along with its
maximum age. The maximum age is changed and displayed again to verify
the new value, then a new cookie is added to the response. However, your
browser may seem to ignore the maximum age; it’s worth playing with
this program and modifying the maximum age value to see the behavior
under different browsers.
JSP summary
This section has only been a brief coverage of JSPs, and yet even with
what was covered here (along with the Java you’ve learned in the rest of
the book, and your own knowledge of HTML) you can begin to write
sophisticated web pages via JSPs. The JSP syntax isn’t meant to be
particularly deep or complicated, so if you understand what was
presented in this section you’re ready to be productive with JSPs. You can
It’s especially nice to have JSPs available, even if your goal is only to
produce servlets. You’ll discover that if you have a question about the
behavior of a servlet feature, it’s much easier and faster to write a JSP test
program to answer that question than it is to write a servlet. Part of the
benefit comes from having to write less code and being able to mix the
display HTML in with the Java code, but the leverage becomes especially
obvious when you see that the JSP Container handles all the
recompilation and reloading of the JSP for you whenever the source is
changed.
As terrific as JSPs are, however, it’s worth keeping in mind that JSP
creation requires a higher level of skill than just programming in Java or
just creating Web pages. In addition, debugging a broken JSP page is not
as easy as debugging a Java program, as (currently) the error messages
are more obscure. This should change as development systems improve,
but we may also see other technologies built on top of Java and the Web
that are better adapted to the skills of the web site designer.
Remote interfaces
RMI makes heavy use of interfaces. When you want to create a remote
object, you mask the underlying implementation by passing around an
interface. Thus, when the client gets a reference to a remote object, what
When you create a remote interface, you must follow these guidelines:
You must explicitly define the constructor for the remote object even if
you’re only defining a default constructor that calls the base-class
constructor. You must write it out since it must throw
RemoteException.
1. Create and install a security manager that supports RMI. The only
one available for RMI as part of the Java distribution is
RMISecurityManager.
2. Create one or more instances of a remote object. Here, you can see
the creation of the PerfectTime object.
3. Register at least one of the remote objects with the RMI remote
object registry for bootstrapping purposes. One remote object can
have methods that produce references to other remote objects. This
allows you to set it up so the client must go to the registry only
once, to get the first remote object.
1. localhost does not work with RMI. Thus, to experiment with RMI
on a single machine, you must provide the name of the machine. To
find out the name of your machine under 32-bit Windows, go to the
control panel and select “Network.” Select the “Identification” tab,
and you’ll see your computer name. In my case, I called my
computer “Peppy.” It appears that capitalization is ignored.
2. RMI will not work unless your computer has an active TCP/IP
connection, even if all your components are just talking to each
other on the local machine. This means that you must connect to
your Internet service provider before trying to run the program or
you’ll get some obscure exception messages.
Even though main( ) exits, your object has been created and registered
so it’s kept alive by the registry, waiting for a client to come along and
request it. As long as the rmiregistry is running and you don’t call
Naming.unbind( ) on your name, the object will be there. For this
reason, when you’re developing your code you need to shut down the
rmiregistry and restart it when you compile a new version of your
remote object.
What’s going on behind the scenes is complex. Any objects that you pass
into or return from a remote object must implement Serializable (if
you want to pass remote references instead of the entire objects, the
object arguments can implement Remote), so you can imagine that the
stubs and skeletons are automatically performing serialization and
When rmic runs successfully, you’ll have two new classes in the
directory:
PerfectTime_Stub.class
PerfectTime_Skel.class
corresponding to the stub and skeleton. Now you’re ready to get the server
and client to talk to each other.
CORBA
In large, distributed applications, your needs might not be satisfied by the
preceding approaches. For example, you might want to interface with
legacy data stores, or you might need services from a server object
regardless of its physical location. These situations require some form of
Remote Procedure Call (RPC), and possibly language independence. This
is where CORBA can help.
CORBA supplies the ability to make remote procedure calls into Java
objects and non-Java objects, and to interface with legacy systems in a
location-transparent way. Java adds networking support and a nice
object-oriented language for building graphical and non-graphical
applications. The Java and OMG object model map nicely to each other;
for example, both Java and CORBA implement the interface concept and
a reference object model.
CORBA fundamentals
The object interoperability specification developed by the OMG is
commonly referred to as the Object Management Architecture (OMA).
The OMA defines two components: the Core Object Model and the OMA
Reference Architecture. The Core Object Model states the basic concepts
of object, interface, operation, and so on. (CORBA is a refinement of the
Core Object Model.) The OMA Reference Architecture defines an
underlying infrastructure of services and mechanisms that allow objects
to interoperate. The OMA Reference Architecture includes the Object
Request Broker (ORB), Object Services (also known as CORBA services),
and common facilities.
The ORB is the communication bus by which objects can request services
from other objects, regardless of their physical location. This means that
what looks like a method call in the client code is actually a complex
operation. First, a connection with the server object must exist, and to
create a connection the ORB must know where the server implementation
code resides. Once the connection is established, the method arguments
must be marshaled, i.e. converted in a binary stream to be sent across a
network. Other information that must be sent are the server machine
name, the server process, and the identity of the server object inside that
process. Finally, this information is sent through a low-level wire
protocol, the information is decoded on the server side, and the call is
executed. The ORB hides all of this complexity from the programmer and
makes the operation almost as simple as calling a method on local object.
An example
The code shown here will not be elaborate because different ORBs have
different ways to access CORBA services, so examples are vendor specific.
(The example below uses JavaIDL, a free product from Sun that comes
with a light-weight ORB, a naming service, and an IDL-to-Java compiler.)
In addition, since Java is young and still evolving, not all CORBA features
are present in the various Java/CORBA products.
The example below shows the IDL description of our ExactTime server:
//: c15:corba:ExactTime.idl
//# You must install idltojava.exe from
//# java.sun.com and adjust the settings to use
//# your local C preprocessor in order to compile
//# This file. See docs at java.sun.com.
module remotetime {
interface ExactTime {
string getTime();
};
}; ///:~
This is a declaration of the ExactTime interface inside the remotetime
namespace. The interface is made up of one single method that gives back
the current time in string format.
The servant object is finally ready for use by clients. At this point, the
server process enters a wait state. Again, this is because it is a transient
servant, so its lifetime is confined to the server process. JavaIDL does not
currently support persistent objects—objects that survive the execution of
the process that creates them.
Now that we have an idea of what the server code is doing, let’s look at the
client code:
//: c15:corba:RemoteTimeClient.java
import remotetime.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
As you can see, there is much more to CORBA than what has been covered
here, but you should get the basic idea. If you want more information
about CORBA, the place to start is the OMG Web site, at www.omg.org.
There you’ll find documentation, white papers, proceedings, and
references to other CORBA sources and products.
Some Java ORB products offer proprietary solutions to this problem. For
example, some implement what is called HTTP Tunneling, while others
have their special firewall features.
However, RMI can be used to call services on remote, non-Java code. All
you need is some kind of wrapper Java object around the non-Java code
on the server side. The wrapper object connects externally to Java clients
Enterprise JavaBeans
Suppose6 you need to develop a multi-tiered application to view and
update records in a database through a Web interface. You can write a
database application using JDBC, a Web interface using JSP/servlets, and
a distributed system using CORBA/RMI. But what extra considerations
must you make when developing a distributed object system rather than
just knowing API’s? Here are the issues:
6 This section was contributed by Robert Castaneda, with help from Dave Bartlett.
These considerations, in addition the business problem that you set out to
solve, can make for a daunting development project. However, all the
issues except for your business problem are redundant—solutions must be
reinvented for every distributed business application.
Sun, along with other leading distributed object vendors, realized that
sooner or later every development team would be reinventing these
particular solutions, so they created the Enterprise JavaBeans
specification (EJB). EJB describes a server-side component model that
tackles all of the considerations mentioned above using a standard
approach that allows developers to create business components called
EJBs that are isolated from low-level “plumbing” code and that focus
solely on providing business logic. Because EJB’s are defined in a
standard way, they can vendor independent.
Role Responsibility
Enterprise Bean The developer responsible for creating reusable
Provider EJB components. These components are
packaged into a special jar file (ejb-jar file).
Application Creates and assembles applications from a
Assembler collection of ejb-jar files. This includes writing
applications that utilize the collection of EJBs
(e.g., servlets, JSP, Swing etc. etc.).
Deployer Takes the collection of ejb-jar files from the
Assembler and/or Bean Provider and deploys
them into a run-time environment: one or more
EJB Containers.
EJB Provides a run-time environment and tools that
Container/Server are used to deploy, administer, and run EJB
Provider components.
System Manages the different components and services
Administrator so that they are configured and they interact
correctly, as well as ensuring that the system is
up and running.
Enterprise Bean
The Enterprise Bean is a Java class that the Enterprise Bean Provider
develops. It implements an Enterprise Bean interface and provides the
implementation of the business methods that the component is to
perform. The class does not implement any authorization, authentication,
multithreading, or transactional code.
Home interface
Every Enterprise Bean that is created must have an associated Home
interface. The Home interface is used as a factory for your EJB. Clients
use the Home interface to find an instance of your EJB or create a new
instance of your EJB.
Deployment descriptor
The deployment descriptor is an XML file that contains information about
your EJB. Using XML allows the deployer to easily change attributes
about your EJB. The configurable attributes defined in the deployment
descriptor include:
• The Home and Remote interface names that are required by your
EJB
• The name to publish into JNDI for your EJBs Home interface
EJB-Jar file
The EJB-Jar file is a normal java jar file that contains your EJB, Home
and Remote interfaces, as well as the deployment descriptor.
EJB operation
Once you have an EJB-Jar file containing the Bean, the Home and
Remote interfaces, and the deployment descriptor, you can fit all of the
pieces together and in the process understand why the Home and Remote
interfaces are needed and how the EJB Container uses them.
The EJB Container implements the Home and Remote interfaces that are
in the EJB-Jar file. As mentioned earlier, the Home interface provides
methods to create and find your EJB. This means that the EJB Container
is responsible for the lifecycle management of your EJB. This level of
indirection allows for optimizations to occur. For example, 5 clients might
simultaneously request the creation of an EJB through a Home Interface,
and the EJB Container would respond by creating only one EJB and
sharing it between all 5 clients. This is achieved through the Remote
All calls to the EJB are ‘proxied’ through the EJB Container via the Home
and Remote interfaces. This indirection is the reason why the EJB
container can control security and transactional behavior.
Types of EJBs
The Enterprise JavaBeans specification defines different types of EJBs
that have different characteristics and behaviors. Two categories of EJBs
have been defined in the specification: Session Beans and Entity Beans,
and each categoriy has variations.
Session Beans
Session Beans are used to represent Use-Cases or Workflow on behalf of a
client. They represent operations on persistent data, but not the persistent
data itself. There are two types of Session Beans, Stateless and Stateful.
All Session Beans must implement the javax.ejb.SessionBean
interface. The EJB Container governs the life of a Session Bean.
Entity Beans
Entity Beans are components that represent persistent data and behavior
of this data. Entity Beans can be shared among multiple clients, the same
way that data in a database can be shared. The EJB Container is
There are two types of Entity Beans: those with Container Managed
persistence and those with Bean-Managed persistence.
Developing an EJB
As an example, the “Perfect Time” example from the previous RMI section
will be implemented as an EJB component. The example will be a simple
Stateless Session Bean.
3. The class should define methods that map directly to the methods
in the Remote interface. Note that the class does not implement the
Remote interface; it mirrors the methods in the Remote interface
but does not throw java.rmi.RemoteException.
5. The return value and arguments of all methods must be valid RMI-
IIOP data types.
<ejb-jar>
<description>Example for Chapter 15</description>
<display-name></display-name>
<small-icon></small-icon>
<large-icon></large-icon>
<enterprise-beans>
<session>
<ejb-name>PerfectTime</ejb-name>
<home>PerfectTimeHome</home>
<remote>PerfectTime</remote>
<ejb-class>PerfectTimeBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
<ejb-client-jar></ejb-client-jar>
</ejb-jar>
///:~
You can see the Component, the Remote interface and the Home interface
defined inside the <session> tag of this deployment descriptor.
Deployment descriptors may be automatically generated using EJB
development tools.
The files must be archived inside a standard Java Archive (JAR) file. The
deployment descriptors should be placed inside the /META-INF sub-
directory of the Jar file.
When a client program wishes to invoke an EJB, it must look up the EJB
component inside JNDI and obtain a reference to the home interface of
the EJB component. The Home interface is used to create an instance of
the EJB.
In this example the client program is a simple Java program, but you
should remember that it could just as easily be a servlet, a JSP or even a
CORBA or RMI distributed object.
//: c15:ejb:PerfectTimeClient.java
// Client program for PerfectTimeBean
EJB summary
The Enterprise JavaBeans specification is a dramatic step forward in the
standardization and simplification of distributed object computing. It is a
major piece of the Java 2 Enterprise Edition (J2EE) platform and is
receiving much support from the distributed object community. Many
tools are currently available or will be available in the near future to help
accelerate the development of EJB components.
This overview was only a brief tour of EJBs. For more information about
the EJB specification you should see the official Enterprise JavaBeans
home page at java.sun.com/products/ejb/, where you can download the
latest specification and the J2EE reference implementation. These can be
used to develop and deploy your own EJB components.
Jini in context
Traditionally, operating systems have been designed with the assumption
that a computer will have a processor, some memory, and a disk. When
What is Jini?
Jini is a set of APIs and network protocols that can help you build and
deploy distributed systems that are organized as federations of services. A
service can be anything that sits on the network and is ready to perform a
useful function. Hardware devices, software, communications channels—
even human users themselves—can be services. A Jini-enabled disk drive,
for example, could offer a “storage” service. A Jini-enabled printer could
offer a “printing” service. A federation of services, then, is a set of services,
currently available on the network, that a client (meaning a program,
service, or user) can bring together to help it accomplish some goal.
The idea behind the word federation is that the Jini view of the network
doesn’t involve a central controlling authority. Because no one service is
in charge, the set of all services available on the network form a
federation—a group composed of equal peers. Instead of a central
authority, Jini’s run-time infrastructure merely provides a way for clients
and services to find each other (via a lookup service, which stores a
directory of currently available services). After services locate each other,
they are on their own. The client and its enlisted services perform their
task independently of the Jini run-time infrastructure. If the Jini lookup
service crashes, any distributed systems brought together via the lookup
service before it crashed can continue their work. Jini even includes a
network protocol that clients can use to find services in the absence of a
lookup service.
"Well-known" interface
Service object
Private
network
Client protocol Service
Summary
Along with Jini for local device networks, this chapter has introduced
some, but not all, of the components that Sun refers to as J2EE: the Java
2 Enterprise Edition. The goal of J2EE is to build a set of tools that allows
the Java developer to build server-based applications much more quickly,
and in a platform-independent way. It’s not only difficult and time-
consuming to build such applications, but it’s especially hard to build
them so that they can be easily ported to other platforms, and also to keep
the business logic separated from the underlying details of the
implementation. J2EE provides a framework to assist in creating server-
based applications; these applications are in demand now, and that
demand appears to be increasing.
Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in Java
Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
2. Create a server that asks for a password, then opens a file and
sends the file over the network connection. Create a client that
connects to this server, gives the appropriate password, then
captures and saves the file. Test the pair of programs on your
machine using the localhost (the local loopback IP address
127.0.0.1 produced by calling
InetAddress.getByName(null)).
10. Create a servlet that adds a cookie to the response object, thereby
storing it on the client’s site. Add code to the servlet that retrieves
and displays the cookie. If you do not have an existing servlet
container, you will need to download, install, and run Tomcat
from jakarta.apache.org in order to run servlets.
13. Create a JSP page that prints a line of text using the <H1> tag. Set
the color of this text randomly, using Java code embedded in the
JSP page. If you do not have an existing JSP container, you will
need to download, install, and run Tomcat from
jakarta.apache.org in order to run JSPs.
14. Modify the maximum age value in Cookies.jsp and observe the
behavior under two different browsers. Also note the difference
between just re-visiting the page, and shutting down and
restarting the browser. If you do not have an existing JSP
container, you will need to download, install, and run Tomcat
from jakarta.apache.org in order to run JSPs.
15. Create a JSP with a field that allows the user to enter the session
expiration time and and a second field that holds data that is
stored in the session. The submit button refreshes the page and
fetches the current expiration time and session data and puts them
in as default values of the aforementioned fields. If you do not
have an existing JSP container, you will need to download, install,
and run Tomcat from jakarta.apache.org in order to run JSPs.
Another way to pose the question of this appendix, if you’re coming from
a programming language so equipped, is “Does Java have pointers?”
Some have claimed that pointers are hard and dangerous and therefore
bad, and since Java is all goodness and light and will lift your earthly
programming burdens, it cannot possibly contain such things. However,
it’s more accurate to say that Java has pointers; indeed, every object
identifier in Java (except for primitives) is one of these pointers, but their
use is restricted and guarded not only by the compiler but by the run-time
system. Or to put it another way, Java has pointers, but no pointer
arithmetic. These are what I’ve been calling “references,” and you can
think of them as “safety pointers,” not unlike the safety scissors of
elementary school—they aren’t sharp, so you cannot hurt yourself without
great effort, but they can sometimes be slow and tedious.
1013
Passing references around
When you pass a reference into a method, you’re still pointing to the same
object. A simple experiment demonstrates this:
//: appendixa:PassReferences.java
// Passing references around.
Aliasing
Aliasing means that more than one reference is tied to the same object, as
in the above example. The problem with aliasing occurs when someone
writes to that object. If the owners of the other references aren’t expecting
that object to change, they’ll be surprised. This can be demonstrated with
a simple example:
One good solution in this case is to simply not do it: don’t consciously
alias more than one reference to an object at the same scope. Your code
will be much easier to understand and debug. However, when you’re
passing a reference in as an argument—which is the way Java is supposed
to work—you automatically alias because the local reference that’s created
If you’re only reading information from an object and not modifying it,
passing a reference is the most efficient form of argument passing. This is
nice; the default way of doing things is also the most efficient. However,
sometimes it’s necessary to be able to treat the object as if it were “local”
so that changes you make affect only a local copy and do not modify the
outside object. Many programming languages support the ability to
automatically make a local copy of the outside object, inside the method 1.
Java does not, but it allows you to produce this effect.
1 In C, which generally handles small bits of data, the default is pass-by-value. C++ had to
follow this form, but with objects pass-by-value isn’t usually the most efficient way. In
addition, coding classes to support pass-by-value in C++ is a big headache.
Having given both camps a good airing, and after saying “It depends on
how you think of a reference,” I will attempt to sidestep the issue. In the
end, it isn’t that important—what is important is that you understand that
passing a reference allows the caller’s object to be changed unexpectedly.
Cloning objects
The most likely reason for making a local copy of an object is if you’re
going to modify that object and you don’t want to modify the caller’s
object. If you decide that you want to make a local copy, you simply use
class Int {
private int i;
public Int(int ii) { i = ii; }
public void increment() { i++; }
public String toString() {
return Integer.toString(i);
}
}
You can see the effect of the shallow copy in the output, where the actions
performed on v2 affect v:
v: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
v: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Not trying to clone( ) the objects contained in the ArrayList is probably
a fair assumption because there’s no guarantee that those objects are
cloneable2.
2 This is not the dictionary spelling of the word, but it’s what is used in the Java library, so
I’ve used it here, too, in some hopes of reducing confusion.
3 You can apparently create a simple counter-example to this statement, like this:
If, however, you’re in a class derived from Object (as all classes are), then
you have permission to call Object.clone( ) because it’s protected and
you’re an inheritor. The base class clone( ) has useful functionality—it
performs the actual bitwise duplication of the derived-class object, thus
acting as the common cloning operation. However, you then need to make
your clone operation public for it to be accessible. So, two key issues
when you clone are:
There are two reasons for the existence of the Cloneable interface.
First, you might have an upcast reference to a base type and not know
whether it’s possible to clone that object. In this case, you can use the
instanceof keyword (described in Chapter 12) to find out whether the
reference is connected to an object that can be cloned:
if(myReference instanceof Cloneable) // ...
The second reason is that mixed into this design for cloneability was the
thought that maybe you didn’t want all types of objects to be cloneable. So
Object.clone( ) verifies that a class implements the Cloneable
interface. If not, it throws a CloneNotSupportedException exception.
So in general, you’re forced to implement Cloneable as part of support
for cloning.
Successful cloning
Once you understand the details of implementing the clone( ) method,
you’re able to create classes that can be easily duplicated to provide a local
copy:
//: appendixa:LocalCopy.java
// Creating local copies with clone().
import java.util.*;
Object.clone( ) figures out how big the object is, creates enough
memory for a new one, and copies all the bits from the old to the new.
This is called a bitwise copy, and is typically what you’d expect a clone( )
method to do. But before Object.clone( ) performs its operations, it first
checks to see if a class is Cloneable—that is, whether it implements the
Cloneable interface. If it doesn’t, Object.clone( ) throws a
CloneNotSupportedException to indicate that you can’t clone it.
Thus, you’ve got to surround your call to super.clone( ) with a try-catch
block, to catch an exception that should never happen (because you’ve
implemented the Cloneable interface).
Whatever you do, the first part of the cloning process should normally be
a call to super.clone( ). This establishes the groundwork for the cloning
operation by making an exact duplicate. At this point you can perform
other operations necessary to complete the cloning.
To know for sure what those other operations are, you need to understand
exactly what Object.clone( ) buys you. In particular, does it
The increment( ) method recursively increments each tag so you can see
the change, and the toString( ) recursively prints each tag. The output is:
s = :a:b:c:d:e
s2 = :a:b:c:d:e
after s.increment, s2 = :a:c:d:e:f
This means that only the first segment is duplicated by Object.clone( ),
therefore it does a shallow copy. If you want the whole snake to be
duplicated—a deep copy—you must perform the additional operations
inside your overridden clone( ).
This example shows what you must do to accomplish a deep copy when
dealing with a composed object:
//: appendixa:DeepCopy.java
// Cloning a composed object.
Adding cloneability
further down a hierarchy
If you create a new class, its base class defaults to Object, which defaults
to noncloneability (as you’ll see in the next section). As long as you don’t
explicitly add cloneability, you won’t get it. But you can add it in at any
layer and it will then be cloneable from that layer downward, like this:
//: appendixa:HorrorFlick.java
// You can insert Cloneability
// at any level of inheritance.
import java.util.*;
class Person {}
It’s worth noting that you must use the Cloneable interface only if you’re
going to call Object’s clone( ), method, since that method checks at run-
time to make sure that your class implements Cloneable. But for
consistency (and since Cloneable is empty anyway) you should
implement it.
Controlling cloneability
You might suggest that, to remove cloneability, the clone( ) method
simply be made private, but this won’t work since you cannot take a
base-class method and make it less accessible in a derived class. So it’s not
that simple. And yet, it’s necessary to be able to control whether an object
can be cloned. There are actually a number of attitudes you can take to
this in a class that you design:
In IsCloneable you can see all the right actions performed for cloning:
clone( ) is overridden and Cloneable is implemented. However, this
clone( ) method and several others that follow in this example do not
catch CloneNotSupportedException, but instead pass it through to
the caller, who must then put a try-catch block around it. In your own
clone( ) methods you will typically catch
CloneNotSupportedException inside clone( ) rather than passing it
through. As you’ll see, in this example it’s more informative to pass the
exceptions through.
Class NoMore attempts to “turn off” cloning in the way that the Java
designers intended: in the derived class clone( ), you throw
CloneNotSupportedException. The clone( ) method in class
But what if the programmer doesn’t follow the “proper” path of calling
super.clone( ) inside the overridden clone( ) method? In BackOn,
you can see how this can happen. This class uses a separate method
duplicate( ) to make a copy of the current object and calls this method
inside clone( ) instead of calling super.clone( ). The exception is never
thrown and the new class is cloneable. You can’t rely on throwing an
exception to prevent making a cloneable class. The only sure-fire solution
is shown in ReallyNoMore, which is final and thus cannot be inherited.
That means if clone( ) throws an exception in the final class, it cannot
be modified with inheritance and the prevention of cloning is assured.
(You cannot explicitly call Object.clone( ) from a class that has an
arbitrary level of inheritance; you are limited to calling super.clone( ),
which has access to only the direct base class.) Thus, if you make any
objects that involve security issues, you’ll want to make those classes
final.
2. Override clone( ).
class FruitQualities {
private int weight;
private int color;
private int firmness;
private int ripeness;
private int smell;
// etc.
FruitQualities() { // Default constructor
// do something meaningful...
}
// Other constructors:
// ...
// Copy constructor:
FruitQualities(FruitQualities f) {
class Seed {
// Members...
Seed() { /* Default constructor */ }
Seed(Seed s) { /* Copy constructor */ }
}
class Fruit {
private FruitQualities fq;
private int seeds;
private Seed[] s;
Fruit(FruitQualities q, int seedCount) {
fq = q;
seeds = seedCount;
s = new Seed[seeds];
for(int i = 0; i < seeds; i++)
s[i] = new Seed();
}
// Other constructors:
// ...
// Copy constructor:
Fruit(Fruit f) {
fq = new FruitQualities(f.fq);
seeds = f.seeds;
// Call all Seed copy-constructors:
for(int i = 0; i < seeds; i++)
s[i] = new Seed(f.s[i]);
// Other copy-construction activities...
}
// To allow derived constructors (or other
// methods) to put in different qualities:
protected void addQualities(FruitQualities q) {
fq = q;
You’ll also see that there’s a Seed class, and that Fruit (which by
definition carries its own seeds)4 contains an array of Seeds.
Finally, notice that each class has a copy constructor, and that each copy
constructor must take care to call the copy constructors for the base class
and member objects to produce a deep copy. The copy constructor is
tested inside the class CopyConstructor. The method ripen( ) takes a
Tomato argument and performs copy-construction on it in order to
duplicate the object:
t = new Tomato(t);
while slice( ) takes a more generic Fruit object and also duplicates it:
f = new Fruit(f);
These are tested with different kinds of Fruit in main( ). Here’s the
output:
In ripen, t is a Tomato
In slice, f is a Fruit
In ripen, t is a Tomato
In slice, f is a Fruit
This is where the problem shows up. After the copy-construction that
happens to the Tomato inside slice( ), the result is no longer a Tomato
object, but just a Fruit. It has lost all of its tomato-ness. Further, when
you take a GreenZebra, both ripen( ) and slice( ) turn it into a
Tomato and a Fruit, respectively. Thus, unfortunately, the copy
constructor scheme is no good to us in Java when attempting to make a
local copy of an object.
4 Except for the poor avocado, which has been reclassified to simply “fat.”
Read-only classes
While the local copy produced by clone( ) gives the desired results in the
appropriate cases, it is an example of forcing the programmer (the author
of the method) to be responsible for preventing the ill effects of aliasing.
What if you’re making a library that’s so general purpose and commonly
used that you cannot make the assumption that it will always be cloned in
the proper places? Or more likely, what if you want to allow aliasing for
efficiency—to prevent the needless duplication of objects—but you don’t
want the negative side effects of aliasing?
If you do need an object that holds a primitive type that can be modified,
you must create it yourself. Fortunately, this is trivial:
//: appendixa:MutableInteger.java
// A changeable wrapper class.
import java.util.*;
class IntValue {
int n;
IntValue(int x) { n = x; }
public String toString() {
return Integer.toString(n);
}
}
class Mutable {
private int data;
public Mutable(int initVal) {
data = initVal;
}
public Mutable add(int x) {
data += x;
return this;
}
public Mutable multiply(int x) {
data *= x;
return this;
The two static methods modify1( ) and modify2( ) show two different
approaches to producing the same result. In modify1( ), everything is
done within the Immutable2 class and you can see that four new
Immutable2 objects are created in the process. (And each time val is
reassigned, the previous object becomes garbage.)
In the method modify2( ), you can see that the first action is to take the
Immutable2 y and produce a Mutable from it. (This is just like calling
clone( ) as you saw earlier, but this time a different type of object is
created.) Then the Mutable object is used to perform a lot of change
operations without requiring the creation of many new objects. Finally,
it’s turned back into an Immutable2. Here, two new objects are created
(the Mutable and the result Immutable2) instead of four.
Immutable Strings
Consider the following code:
//: appendixa:Stringer.java
Looking at the definition for upcase( ), you can see that the reference
that’s passed in has the name s, and it exists for only as long as the body
of upcase( ) is being executed. When upcase( ) completes, the local
reference s vanishes. upcase( ) returns the result, which is the original
string with all the characters set to uppercase. Of course, it actually
returns a reference to the result. But it turns out that the reference that it
returns is for a new object, and the original q is left alone. How does this
happen?
Implicit constants
If you say:
String s = "asdf";
String x = Stringer.upcase(s);
do you really want the upcase( ) method to change the argument? In
general, you don’t, because an argument usually looks to the reader of the
code as a piece of information provided to the method, not something to
be modified. This is an important guarantee, since it makes code easier to
write and understand.
Since String objects are immutable, you can alias to a particular String
as many times as you want. Because it’s read-only there’s no possibility
that one reference will change something that will affect the other
references. So a read-only object solves the aliasing problem nicely.
It also seems possible to handle all the cases in which you need a modified
object by creating a brand new version of the object with the
modifications, as String does. However, for some operations this isn’t
efficient. A case in point is the operator ‘+’ that has been overloaded for
String objects. Overloading means that it has been given an extra
meaning when used with a particular class. (The ‘+’ and ‘+=’ for String
are the only operators that are overloaded in Java, and Java does not
allow the programmer to overload any others)5.
When used with String objects, the ‘+’ allows you to concatenate Strings
together:
String s = "abc" + foo + "def" + Integer.toString(47);
5 C++ allows the programmer to overload operators at will. Because this can often be a
complicated process (see Chapter 10 of Thinking in C++, 2nd edition, Prentice-Hall, 2000),
the Java designers deemed it a “bad” feature that shouldn’t be included in Java. It wasn’t
so bad that they didn’t end up doing it themselves, and ironically enough, operator
overloading would be much easier to use in Java than in C++. This can be seen in Python
(see www.Python.org) which has garbage collection and straightforward operator
overloading.
This would certainly work, but it requires the creation of a lot of String
objects just to put together this new String, and then you have a bunch of
the intermediate String objects that need to be garbage-collected. I
suspect that the Java designers tried this approach first (which is a lesson
in software design—you don’t really know anything about a system until
you try it out in code and get something working). I also suspect they
discovered that it delivered unacceptable performance.
You can see that every String method carefully returns a new String
object when it’s necessary to change the contents. Also notice that if the
contents don’t need changing the method will just return a reference to
the original String. This saves storage and overhead.
Summary
Because everything is a reference in Java, and because every object is
created on the heap and garbage-collected only when it is no longer used,
the flavor of object manipulation changes, especially when passing and
returning objects. For example, in C or C++, if you wanted to initialize
some piece of storage in a method, you’d probably request that the user
pass the address of that piece of storage into the method. Otherwise you’d
have to worry about who was responsible for destroying that storage.
Thus, the interface and understanding of such methods is more
complicated. But in Java, you never have to worry about responsibility or
whether an object will still exist when it is needed, since that is always
1. You always take the efficiency hit for the extra memory
management (although this can be quite small), and there’s always
a slight amount of uncertainty about the time something can take
to run (since the garbage collector can be forced into action
whenever you get low on memory). For most applications, the
benefits outweigh the drawbacks, and particularly time-critical
sections can be written using native methods (see Appendix B).
Some people say that cloning in Java is a botched design, and to heck with
it, so they implement their own version of cloning6 and never call the
Object.clone( ) method, thus eliminating the need to implement
Cloneable and catch the CloneNotSupportedException. This is
certainly a reasonable approach and since clone( ) is supported so rarely
within the standard Java library, it is apparently a safe one as well. But as
long as you don’t call Object.clone( ) you don’t need to implement
Cloneable or catch the exception, so that would seem acceptable as well.
6 Doug Lea, who was helpful in resolving this issue, suggested this to me, saying that he
simply creates a function called duplicate( ) for each class.
The Java language and its standard API are rich enough
to write full-fledged applications. But in some cases you
must call non-Java code; for example, if you want to
access operating-system-specific features, interface with
special hardware devices, reuse a preexisting, non-Java
code base, or implement time-critical sections of code.
Interfacing with non-Java code requires dedicated support in the
compiler and in the Virtual Machine, and additional tools to map the Java
code to the non-Java code. The standard solution for calling non-Java
code that is provided by JavaSoft is called the Java Native Interface,
which will be introduced in this appendix. This is not an in-depth
treatment, and in some cases you’re assumed to have partial knowledge of
the related concepts and techniques.
JNI is a fairly rich programming interface that allows you to call native
methods from a Java application. It was added in Java 1.1, maintaining a
certain degree of compatibility with its Java 1.0 equivalent: the native
method interface (NMI). NMI has design characteristics that make it
unsuitable for adoption across all virtual machines. For this reason, future
versions of the language might no longer support NMI, and it will not be
covered here.
♦ Create, inspect, and update Java objects (including arrays and Strings)
1065
♦ Catch and throw exceptions
Thus, virtually everything you can do with classes and objects in ordinary
Java you can also do in native methods.
The first step is to write the Java code declaring a native method and its
arguments:
//: appendixb:ShowMessage.java
public class ShowMessage {
private native void ShowMessage(String msg);
static {
System.loadLibrary("MsgImpl");
// Linux hack, if you can't get your library
// path set in your environment:
// System.load(
// "/home/bruce/tij2/appendixb/MsgImpl.so");
}
public static void main(String[] args) {
ShowMessage app = new ShowMessage();
app.ShowMessage("Generated with JNI");
}
} ///:~
The native method declaration is followed by a static block that calls
System.loadLibrary( ) (which you could call at any time, but this style
is more appropriate). System.loadLibrary( ) loads a DLL in memory
and links to it. The DLL must be in your system library path. The file
name extension is automatically added by the JVM depending on the
platform.
#ifndef _Included_ShowMessage
#define _Included_ShowMessage
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: ShowMessage
* Method: ShowMessage
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL
Java_ShowMessage_ShowMessage
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
The remaining arguments represent the Java objects passed into the
native method call. Primitives are also passed in this way, but they come
in by value.
In the following sections we’ll explain this code by looking at the ways that
you access and control the JVM from inside a native method.
Through the JNIEnv argument, the programmer has access to a large set
of functions. These functions can be grouped into the following
categories:
The number of JNI functions is quite large and won’t be covered here.
Instead, I’ll show the rationale behind the use of these functions. For
more detailed information, consult your compiler’s JNI documentation.
If you take a look at the jni.h header file, you’ll see that inside the #ifdef
__cplusplus preprocessor conditional, the JNIEnv_ structure is
defined as a class when compiled by a C++ compiler. This class contains a
number of inline functions that let you access the JNI functions with an
easy and familiar syntax. For example, the line of C++ code in the
preceding example:
env->ReleaseStringUTFChars(jMsg, msg);
could also be called from C like this:
(*env)->ReleaseStringUTFChars(env, jMsg, msg);
To pass objects, use the ordinary Java syntax when declaring the native
method. In the example below, MyJavaClass has one public field and
one public method. The class UseObjects declares a native method that
takes an object of class MyJavaClass. To see if the native method
manipulates its argument, the public field of the argument is set, the
native method is called, and then the value of the public field is printed.
//: appendixb:UseObjects.java
To access a Java field or method, you must first obtain its identifier using
GetFieldID( ) for fields and GetMethodID( ) for methods. These
functions take the class object, a string containing the element name, and
a string that gives type information: the data type of the field, or signature
information for a method (details can be found in the JNI
documentation). These functions return an identifier that you use to
access the element. This approach might seem convoluted, but your native
method has no knowledge of the internal layout of the Java object.
Instead, it must access fields and methods through indexes returned by
the JVM. This allows different JVMs to implement different internal
object layouts with no impact on your native methods.
If you run the Java program, you’ll see that the object that’s passed from
the Java side is manipulated by your native method. But what exactly is
passed? A pointer or a Java reference? And what is the garbage collector
doing during native method calls?
Since these references are created and subsequently destroyed every time
the function is called, you cannot make local copies in your native
♦ Throw( )
Throws an existing exception object. Used in native methods to rethrow
an exception.
♦ ThrowNew( )
Generates a new exception object and throws it.
♦ ExceptionOccurred( )
Determines if an exception was thrown and not yet cleared.
♦ ExceptionDescribe( )
Prints an exception and the stack trace.
♦ ExceptionClear( )
Clears a pending exception.
♦ FatalError( )
Raises a fatal error. Does not return.
You must ensure that the exception is cleared, because otherwise the
results will be unpredictable if you call a JNI function while an exception
is pending. There are few JNI functions that are safe to call during an
exception; among these, of course, are all the exception handling
functions.
Also, you should never pass the JNIEnv pointer across threads, since the
internal structure it points to is allocated on a per-thread basis and
contains information that makes sense only in that particular thread.
Additional information
You can find further introductory material, including a C (rather than
C++) example and discussion of Microsoft issues, in Appendix A of the
first edition of this book, which can be found on the CD ROM bound in
with this book, or in a free download from www.BruceEckel.com. More
extensive information is available at java.sun.com (in the search engine,
select “training & tutorials” for keywords “native methods”). Chapter 11 of
Core Java 2, Volume II, by Horstmann & Cornell (Prentice-Hall, 2000)
gives excellent coverage of native methods.
Design
1. Elegance always pays off. In the short term it might seem like it
takes much longer to come up with a truly graceful solution to a
problem, but when it works the first time and easily adapts to new
situations instead of requiring hours, days, or months of struggle,
you’ll see the rewards (even if no one can measure them). Not only
does it give you a program that’s easier to build and debug, but it’s
also easier to understand and maintain, and that’s where the
financial value lies. This point can take some experience to
understand, because it can appear that you’re not being productive
while you’re making a piece of code elegant. Resist the urge to
hurry; it will only slow you down.
2. First make it work, then make it fast. This is true even if you
are certain that a piece of code is really important and that it will be
a principal bottleneck in your system. Don’t do it. Get the system
going first with as simple a design as possible. Then if it isn’t going
fast enough, profile it. You’ll almost always discover that “your”
1077
bottleneck isn’t the problem. Save your time for the really
important stuff.
7. Automate everything. Write the test code first (before you write
the class), and keep it with the class. Automate the running of your
tests through a makefile or similar tool. This way, any changes can
be automatically verified by running the test code, and you’ll
immediately discover errors. Because you know that you have the
safety net of your test framework, you will be bolder about making
8. Write the test code first (before you write the class) in
order to verify that your class design is complete. If you
can’t write test code, you don’t know what your class looks like. In
addition, the act of writing the test code will often flush out
additional features or constraints that you need in the class—these
features or constraints don’t always appear during analysis and
design. Tests also provide example code showing how your class
can be used.
12. Watch for long argument lists. Method calls then become
difficult to write, read, and maintain. Instead, try to move the
method to a class where it is (more) appropriate, and/or pass
objects in as arguments.
18. Read your classes aloud to make sure they’re logical. Refer
to the relationship between a base class and derived class as “is-a”
and member objects as “has-a.”
28. Objects should not simply hold some data. They should also
have well-defined behaviors. (Occasionally, “data objects” are
appropriate, but only when used expressly to package and
transport a group of items when a generalized container is
innappropriate.)
31. Watch out for variance. Two semantically different objects may
have identical actions, or responsibilities, and there is a natural
temptation to try to make one a subclass of the other just to benefit
from inheritance. This is called variance, but there’s no real
justification to force a superclass/subclass relationship where it
doesn’t exist. A better solution is to create a general base class that
produces an interface for both as derived classes—it requires a bit
more space, but you still benefit from inheritance, and will
probably make an important discovery about the design.
34. Watch out for “analysis paralysis.” Remember that you must
usually move forward in a project before you know everything, and
that often the best and fastest way to learn about some of your
unknown factors is to go to the next step rather than trying to
figure it out in your head. You can’t know the solution until you
have the solution. Java has built-in firewalls; let them work for
Implementation
36. In general, follow the Sun coding conventions. These are
available at
java.sun.com/docs/codeconv/index.html (the code in this book
follows these conventions as much as I was able). These are used
for what constitutes arguably the largest body of code that the
largest number of Java programmers will be exposed to. If you
doggedly stick to the coding style you’ve always used, you will make
it harder for your reader. Whatever coding conventions you decide
on, ensure they are consistent throughout the project. There is a
free tool to automatically reformat Java code at:
home.wtal.de/software-solutions/jindent.
Books
Thinking in Java, 1st Edition. Available as fully-indexed, color-
syntax-highlighted HTML on the CD ROM bound in with this book, or as
a free download from www.BruceEckel.com. Includes older material and
material that was not considered interesting enough to carry through to
the 2nd edition.
1091
Java in a Nutshell: A Desktop Quick Reference, 2nd Edition, by
David Flanagan (O’Reilly, 1997). A compact summary of the online Java
documentation. Personally, I prefer to browse the docs from java.sun.com
online, especially since they change so often. However, many folks still
like printed documentation and this fits the bill; it also provides more
discussion than the online documents.
UML Toolkit, by Hans-Erik Eriksson & Magnus Penker, (John Wiley &
Sons, 1997). Explains UML and how to use it, and has a case study in
Java. An accompanying CD ROM contains the Java code and a cut-down
version of Rational Rose. An excellent introduction to UML and how to
use it to build a real system.
Before you choose any method, it’s helpful to gain perspective from those
who are not trying to sell one. It’s easy to adopt a method without really
understanding what you want out of it or what it will do for you. Others
are using it, which seems a compelling reason. However, humans have a
strange little psychological quirk: If they want to believe something will
solve their problems, they’ll try it. (This is experimentation, which is
good.) But if it doesn’t solve their problems, they may redouble their
efforts and begin to announce loudly what a great thing they’ve
discovered. (This is denial, which is not good.) The assumption here may
be that if you can get other people in the same boat, you won’t be lonely,
even if it’s going nowhere (or sinking).
This is not to suggest that all methodologies go nowhere, but that you
should be armed to the teeth with mental tools that help you stay in
experimentation mode (“It’s not working; let’s try something else”) and
out of denial mode (“No, that’s not really a problem. Everything’s
wonderful, we don’t need to change”). I think the following books, read
before you choose a method, will provide you with these tools.
Python
Learning Python, by Mark Lutz and David Ascher (O’Reilly, 1999). A
nice programmer’s introduction to what is rapidly becoming my favorite
language, an excellent companion to Java. The book includes an
Black Belt C++, the Master’s Collection, Bruce Eckel, editor (M&T
Books, 1994). Out of print. A collection of chapters by various C++
luminaries based on their presentations in the C++ track at the Software
Development Conference, which I chaired. The cover on this book
stimulated me to gain control over all future cover designs.
1099
optimize loading · 793; parameter · 697; base class · 260, 275, 315; abstract base
placing inside a Web page · 695; class · 326; base-class interface · 320;
restrictions · 692 constructor · 332; constructors and
Applet: combined with application · 839; exceptions · 281; initialization · 278
initialization parameters · 839 Basic: Microsoft Visual Basic · 800
appletviewer · 698 basic concepts of object-oriented
application: application builder · 800; programming (OOP) · 29
application framework · 394; combined BASIC language · 92
applets and applications · 700; BasicArrowButton · 735
combined with Applet · 839; windowed beanbox Bean testing tool · 817
applications · 700 BeanInfo: custom BeanInfo · 818
application framework, and applets · 694 Beans: and Borland’s Delphi · 800; and
archive tag, for HTML and JAR files · 793 Microsoft’s Visual Basic · 800; and
argument: constructor · 193; final · 298, multithreading · 854; application
577; passing a reference into a method · builder · 800; beanbox Bean testing tool
1014; variable argument lists (unknown · 817; bound properties · 818;
quantity and type of arguments) · 235 component · 801; constrained
array · 407; associative array · 477; properties · 818; custom BeanInfo · 818;
associative array, Map · 442; bounds custom property editor · 818; custom
checking · 232; comparing arrays · 431; property sheet · 818; events · 801;
copying an array · 429; dynamic EventSetDescriptors · 808;
aggregate initialization syntax · 412; FeatureDescriptor · 818; getBeanInfo( )
element comparisons · 431; first-class · 805; getEventSetDescriptors( ) · 808;
objects · 409; initialization · 231; length getMethodDescriptors( ) · 808;
· 232, 409; multidimensional · 236; of getName( ) · 808;
objects · 409; of primitives · 409; return getPropertyDescriptors( ) · 808;
an array · 413 getPropertyType( ) · 808;
ArrayList · 456, 463, 467, 500, 505; add( ) getReadMethod( ) · 808;
· 450; and deep copying · 1030; get( ) · getWriteMethod( ) · 808; indexed
450, 456; size( ) · 451; type-conscious property · 818; Introspector · 805; JAR
ArrayList · 454; used with HashMap · files for packaging · 816; manifest file ·
652 816; Method · 808; MethodDescriptors ·
Arrays class, container utility · 415 808; naming convention · 802;
Arrays.asList( ) · 519 properties · 801; PropertyChangeEvent ·
Arrays.binarySearch( ) · 437 818; PropertyDescriptors · 808;
Arrays.fill( ) · 428 ProptertyVetoException · 818; reflection
assigning objects · 134 · 801, 804; Serializable · 814; visual
assignment · 134 programming · 800
associative array · 439, 477 Beck, Kent · 1093
associative arrays (Maps) · 442 Bill Joy · 141
auto-decrement operator · 139 binary: numbers · 156; operators · 146
auto-increment operator · 139 binary numbers, printing · 150
automatic type conversion · 273 binarySearch( ) · 437
available( ) · 598 bind( ) · 976
binding: dynamic binding · 316; dynamic,
late, or run-time binding · 311; early ·
B 45; late · 45; late binding · 316; method
call binding · 315; run-time binding ·
316
bag · 440
BitSet · 522
base: types · 39
bitwise: AND · 154; AND operator (&) ·
base 16 · 156
146; EXCLUSIVE OR XOR (^) · 146;
base 8 · 156
1100
NOT ~ · 146; operators · 146; OR · 154; double to integral, truncation · 186;
OR operator (|) · 146 operators · 154
bitwise copy · 1024 catch: catching an exception · 534;
blank final · 297 catching any exception · 543; keyword ·
blocking: and available( ) · 598; and 535
threads · 859; on I/O · 869 CD ROM for book · 20
Booch, Grady · 1093 CGI: Common-Gateway Interface · 948
book: errors, reporting · 23; updates of the change: vector of change · 397
book · 22 CharArrayReader · 590
boolean: operators that won’t work with CharArrayWriter · 590
boolean · 141 check box · 748
Boolean · 169; algebra · 146; and casting · CheckedInputStream · 606
155; vs. C and C++ · 144 CheckedOutputStream · 606
BorderLayout · 713 Checksum · 608
Borland · 820; Delphi · 800 class · 32, 262; abstract class · 326; access ·
bound properties · 818 263; anonymous inner · 709;
bounds checking, array · 232 anonymous inner class · 370, 576, 875;
Box, for BoxLayout · 718 anonymous inner class and constructors
BoxLayout · 717 · 375; base class · 260, 275, 315; browser
break keyword · 175 · 263; class hierarchies and exception
browser: class browser · 263 handling · 567; class literal · 664, 669;
BufferedInputStream · 586 creators · 35; defining the interface · 88;
BufferedOutputStream · 588 derived class · 315; equivalence, and
BufferedReader · 563, 591, 597 instanceof/isInstance( ) · 672; final
BufferedWriter · 591, 599 classes · 301; inheritance diagrams ·
business objects/logic · 796 293; inheriting from an abstract class ·
button: creating your own · 730; radio 326; inheriting from inner classes · 384;
button · 750 initialization & class loading · 304;
button, Swing · 706 initialization of data members · 220;
ButtonGroup · 736, 750 initializing members at point of
buttons · 734 definition · 221; initializing the base
ByteArrayInputStream · 582 class · 278; inner class · 365; inner class
ByteArrayOutputStream · 583 nesting within any arbitrary scope · 372;
inner classes · 799; inner classes &
access rights · 376; inner classes and
C overriding · 385; inner classes and super
· 385; inner classes and Swing · 722;
inner classes and upcasting · 368; inner
C/C++, interfacing with · 1065
classes in methods & scopes · 370; inner
C++ · 141; copy constructor · 1042;
classes, identifiers and .class files · 387;
Standard Container Library aka STL ·
instance of · 31; intializing the derived
440; strategies for transition to · 93;
class · 278; keyword · 38; loading · 305;
templates · 455; vector class, vs. array
member initialization · 273; multiply-
and ArrayList · 408; why it succeeds · 91
nested · 383; order of initialization ·
callback · 432, 575, 708
223; private inner classes · 397; public
callbacks: and inner classes · 391
class, and compilation units · 245; read-
capacity, of a HashMap or HashSet · 491
only classes · 1047; referring to the
capitalization: Java capitalization style
outer class object in an inner class · 381;
source-code checking tool · 645; of
static inner classes · 379; style of
package names · 116
creating classes · 262; subobject · 278
case statement · 183
Class · 737; Class object · 633, 662, 848;
cast · 47, 201, 661; and containers · 450;
forName( ) · 664, 727; getClass( ) · 544;
and primitive types · 170; from float or
1101
getConstructors( ) · 681; getInterfaces( ) compareTo( ), in java.lang.Comparable ·
· 676; getMethods( ) · 681; getName( ) · 432
677; getSuperclass( ) · 676; isInstance · comparing arrays · 431
671; isInterface( ) · 677; newInstance( ) · compilation unit · 245
676; printInfo( ) · 677; RTTI using the compile-time constant · 294
Class object · 674 compiling a Java program · 121
Class object · 227 component, and JavaBeans · 801
ClassCastException · 345, 666 composition · 37, 271; and cloning · 1027;
classpath · 248, 699; and rmic · 979 and design · 340; and dynamic behavior
class-responsibility-collaboration (CRC) change · 341; choosing composition vs.
cards · 79 inheritance · 288; combining
cleanup: and garbage collector · 283; composition & inheritance · 281; vs.
performing · 209; with finally · 554 inheritance · 294, 642
cleanup, guaranteeing with finalize( ) · 214 compression: compression library · 606
client programmer · 35; vs. library creator · concept, high · 75
243 ConcurrentModificationException · 515
client, network · 907 conditional operator · 151
clipboard: system clipboard · 790 conference, Software Development
clone( ) · 1021; and composition · 1027; Conference · 10
and inheritance · 1034; Object.clone( ) · Console: Swing display framework in
1025; removing/turning off cloneability com.bruceeckel.swing · 702
· 1036; super.clone( ) · 1025, 1041; console input · 597
supporting cloning in derived classes · const, in C++ · 1053
1036 constant: compile-time constant · 294;
Cloneable interface · 1022 folding · 294; groups of constant values ·
CloneNotSupportedException · 1024 359; implicit constants, and String ·
close( ) · 597 1053
closure, and inner classes · 391 constrained properties · 818
code: calling non-Java code · 1065; coding constructor · 191; and anonymous inner
standards · 22, 1077; organization · 255; classes · 370; and exception handling ·
re-use · 271 562; and exceptions · 561; and finally ·
codebase · 697 562; and overloading · 194; and
Collection · 440 polymorphism · 330; arguments · 193;
collection class · 407 base-class constructor · 332; base-class
Collections · 511 constructors and exceptions · 281;
Collections.enumeration( ) · 520 behavior of polymorphic methods inside
Collections.fill( ) · 443 constructors · 337; C++ copy
Collections.reverseOrder() · 434 constructor · 1042; calling base-class
collision: name · 250 constructors with arguments · 280;
collisions, during hashing · 488 calling from other constructors · 205;
com.bruceeckel.swing · 703 default · 202; default constructors · 196;
combo box · 751 initialization during inheritance and
comma operator · 152, 175 composition · 281; name · 192; no-arg
Command Pattern · 575 constructors · 196; order of constructor
comments: and embedded documentation calls with inheritance · 330; return value
· 122 · 193; static construction clause · 228;
common interface · 325 synthesized default constructor access ·
common pitfalls when using operators · 681
153 Constructor: for reflection · 678
Common-Gateway Interface (CGI) · 948 consulting & mentoring provided by Bruce
Comparable · 432, 475 Eckel · 23
Comparator · 434, 475
1102
container: class · 407, 439; of primitives · deep copy · 1020, 1027; and ArrayList ·
412 1030; using serialization to perform
container classes, utilities for · 444 deep copying · 1032
continue keyword · 175 default constructor · 196, 202;
control: access · 36 synthesizing a default constructor · 279
control framework, and inner classes · 394 default constructor, access the same as the
controlling access · 267 class · 681
conversion: automatic · 273; narrowing default keyword, in a switch statement ·
conversion · 155, 201; widening 183
conversion · 155 default package · 257
cookies: and JSP · 971 DefaultMutableTreeNode · 784
cookies, and servlets · 955 defaultReadObject( ) · 629
copy: deep copy · 1020; shallow copy · DefaultTreeModel · 784
1019 defaultWriteObject( ) · 629
copying an array · 429 DeflaterOutputStream · 606
CORBA · 980 Delphi, from Borland · 800
costs, startup · 95 Demarco, Tom · 1095
coupling · 537 dequeue · 440
CRC, class-responsibility-collaboration derived: derived class · 315; derived class,
cards · 79 initializing · 278; types · 39
CRC32 · 608 design · 342; adding more methods to a
createStatement( ) · 930 design · 268; analysis and design,
critical section, and synchronized block · object-oriented · 71; and composition ·
852 340; and inheritance · 339; and
mistakes · 268; five stages of object
design · 82; library design · 243; of
D object hierarchies · 307; patterns · 86,
94
design patterns · 266; decorator · 585;
daemon threads · 840
singleton · 266
data: final · 294; primitive data types and
destroy( ) · 877
use with operators · 159; static
destructor · 208, 209, 554; Java doesn’t
initialization · 225
have one · 283
data type: equivalence to class · 33
development, incremental · 291
database: flat-file database · 932; Java
diagram: inheritance · 47; use case · 77
DataBase Connectivity (JDBC) · 927;
diagram, class inheritance diagrams · 293
relational database · 933; URL · 928
dialog box · 771
DatabaseMetaData · 938
dialog, file · 776
DataFlavor · 792
dialog, tabbed · 755
Datagram · 923; User Datagram Protocol
dictionary · 477
(UDP) · 923
digital signing · 692
DataInput · 593
directory: and packages · 254; creating
DataInputStream · 586, 591, 597, 599
directories and paths · 578; lister · 574
DataOutput · 593
display framework, for Swing · 702
DataOutputStream · 588, 592, 599
dispose( ) · 772
dead, Thread · 859
division · 137
deadlock, multithreading · 865, 872
documentation: comments & embedded
death condition, and finalize( ) · 214
documentation · 122
decorator design pattern · 585
Domain Name System (DNS) · 905
decoupling: via polymorphism · 46
dotted quad · 905
decoupling through polymorphism · 311
double, literal value marker (D) · 156
decrement operator · 139
do-while · 173
1103
downcast · 293, 343, 666; type-safe 543; changing the point of origin of the
downcast in run-time type identification exception · 547; class hierarchies · 567;
· 665 constructors · 562; creating your own ·
Drawing lines in Swing · 768 537; design issues · 565; Error class ·
drop-down list · 751 549; Exception class · 549; exception
dynamic: behavior change with handler · 535; exception handling · 531;
composition · 341; binding · 311, 316 exception matching · 566;
dynamic aggregate initialization syntax for FileNotFoundException · 565;
arrays · 412 fillInStackTrace( ) · 545; finally · 552;
guarded region · 535; handler · 532;
handling · 283; losing an exception,
E pitfall · 557; NullPointerException · 550;
printStackTrace( ) · 545; restrictions ·
558; re-throwing an exception · 545;
early binding · 45, 315
RuntimeException · 550; specification ·
East, BorderLayout · 713
542; termination vs. resumption · 536;
editor, creating one using the Swing
Throwable · 543; throwing an exception
JTextPane · 747
· 533; try · 554; try block · 535; typical
efficiency: and arrays · 408; and final ·
uses of exceptions · 568
302; and threads · 828; when using the
exceptional condition · 532
synchronized keyword · 853
exceptions: and JNI · 1074
EJB · 990
executeQuery( ) · 930
elegance, in programming · 87
Exponential notation · 156
else keyword · 171
extending a class during inheritance · 41
encapsulation · 261
extends · 260, 277, 342; and interface ·
Enterprise JavaBeans (EJB) · 990
359; keyword · 275
enum, groups of constant values in C &
extensible: program · 320
C++ · 359
extension: pure inheritance vs. extension ·
Enumeration · 520
341
equals( ) · 142, 475; and hashed data
extension, sign · 147
structures · 485; overriding for
extension, zero · 147
HashMap · 484; vs. == · 645
Externalizable · 620; alternative approach
equivalence: == · 141; object equivalence ·
to using · 626
141
Extreme Programming (XP) · 88, 1093
error: handling with exceptions · 531;
recovery · 568; reporting errors in book
· 23; standard error stream · 538
event: event-driven system · 394; F
JavaBeans · 801; multicast · 796;
multicast event and JavaBeans · 854; fail fast containers · 515
responding to a Swing event · 707; false · 143
Swing event model · 794; unicast · 796 FeatureDescriptor · 818
event listener · 722; order of execution · Field, for reflection · 678
796 fields, initializing fields in interfaces · 361
event model, Swing · 722 FIFO · 472
event-driven programming · 707 file: characteristics of files · 578; File.list( )
events and listeners · 723 · 574; incomplete output files, errors
EventSetDescriptors · 808 and flushing · 599; JAR file · 245
evolution, in program development · 85 File · 582, 592, 655; class · 574
exception: and base-class constructors · file dialogs · 776
281; and constructors · 561; and File Transfer Protocol (FTP) · 699
inheritance · 558, 566; catching an FileDescriptor · 582
exception · 534; catching any exception · FileInputReader · 597
1104
FileInputStream · 582
FilenameFilter · 574, 653
G
FileNotFoundException · 565
FileOutputStream · 583 garbage collection · 207, 210, 333; and
FileReader · 563, 590 cleanup · 283; and native method
FileWriter · 590, 599 execution · 1073; forcing finalization ·
fillInStackTrace( ) · 545 286; how the collector works · 215;
FilterInputStream · 582 order of object reclamation · 286;
FilterOutputStream · 583 reachable objects · 495; setting
FilterReader · 591 references to null to allow cleanup · 397
FilterWriter · 591 generator · 443
final · 350; and efficiency · 302; and generator object, to fill arrays and
private · 299; and static · 294; argument containers · 416
· 298, 577; blank finals · 297; classes · get( ), ArrayList · 450, 456
301; data · 294; keyword · 294; method · get( ), HashMap · 481
316; methods · 299, 339; static getBeanInfo( ) · 805
primitives · 296; with object references · getBytes( ) · 598
295 getClass( ) · 544, 674
finalize( ) · 207, 566; and inheritance · getConstructor( ) · 737
333; and super · 335; calling directly · getConstructors( ) · 681
210; order of finalization of objects · 336 getContentPane( ) · 695
finally · 283, 286; and constructors · 562; getContents( ) · 792
keyword · 552; pitfall · 557 getEventSetDescriptors( ) · 808
finding .class files during loading · 247 getFloat( ) · 930
flat-file database · 932 getInputStream( ) · 909
flavor, clipboard · 790 getInt( ) · 930
float, literal value marker(F) · 156 getInterfaces( ) · 676
floating point: true and false · 144 getMethodDescriptors( ) · 808
FlowLayout · 714 getMethods( ) · 681
flushing output files · 599 getModel( ) · 784
focus traversal · 691 getName( ) · 677, 808
folding, constant · 294 getOutputStream( ) · 909
for keyword · 173 getPriority( ) · 878
forName( ) · 664, 727 getPropertyDescriptors( ) · 808
FORTRAN · 156 getPropertyType( ) · 808
forward referencing · 222 getReadMethod( ) · 808
Fowler, Martin · 72, 85, 1093 getSelectedValues( ) · 753
framework: application framework and getState( ) · 765
applets · 694; control framework and getString( ) · 930
inner classes · 394 getSuperclass( ) · 676
friendly · 243, 368; and interface · 350; getTransferData( ) · 792
and protected · 290; less accessible than getTransferDataFlavors( ) · 792
protected · 335 getWriteMethod( ) · 808
FTP: File Transfer Protocol (FTP) · 699 Glass, Robert · 1094
function: member function · 35; overriding glue, in BoxLayout · 717
· 42 goto: lack of goto in Java · 177
functor · 575 graphical user interface (GUI) · 394, 689
graphics · 776
Graphics · 768
greater than (>) · 141
greater than or equal to (>=) · 141
GridBagLayout · 716
GridLayout · 715, 894
1105
guarded region, in exception handling · CheckedInputStream · 606;
535 CheckedOutputStream · 606; close( ) ·
GUI: graphical user interface · 394, 689 597; compression library · 606; console
GUI builders · 690 input · 597; controlling the process of
guidelines: object development · 83 serialization · 619; DataInput · 593;
guidelines, coding standards · 1077 DataInputStream · 586, 591, 597, 599;
GZIPInputStream · 606 DataOutput · 593; DataOutputStream ·
GZIPOutputStream · 606 588, 592, 599; DeflaterOutputStream ·
606; directory lister · 574; directory,
creating directories and paths · 578;
H Externalizable · 620; File · 582, 592,
655; File class · 574; File.list( ) · 574;
FileDescriptor · 582; FileInputReader ·
handler, exception · 535
597; FileInputStream · 582;
hardware devices, interfacing with · 1065
FilenameFilter · 574, 653;
has-a · 37
FileOutputStream · 583; FileReader ·
has-a relationship, composition · 289
563, 590; FileWriter · 590, 599;
hash code · 477, 488
FilterInputStream · 582;
hash function · 488
FilterOutputStream · 583; FilterReader ·
hashCode( ) · 473, 477; and hashed data
591; FilterWriter · 591; from standard
structures · 485; issues when writing ·
input · 602; GZIPInputStream · 606;
492; overriding for HashMap · 484
GZIPOutputStream · 606;
hashing · 485; external chaining · 488;
InflaterInputStream · 606; input · 581;
perfect hashing function · 488
InputStream · 581, 913;
HashMap · 476, 500, 733; used with
InputStreamReader · 589, 590, 913;
ArrayList · 652
internationalization · 590; library · 573;
HashSet · 473, 506
lightweight persistence · 613;
Hashtable · 510, 521
LineNumberInputStream · 586;
hasNext( ), Iterator · 457
LineNumberReader · 591; mark( ) · 593;
Hexadecimal · 156
mkdirs( ) · 580; nextToken( ) · 654;
hiding: implementation · 35
ObjectOutputStream · 614; output · 581;
hiding, implementation · 261
OutputStream · 581, 583, 913;
high concept · 75
OutputStreamWriter · 589, 590, 913;
HTML · 948; name · 839; param · 839;
pipe · 581; piped stream · 869; piped
value · 839
streams · 602; PipedInputStream · 582;
HTML on Swing components · 779
PipedOutputStream · 582, 583;
PipedReader · 590; PipedWriter · 590;
PrintStream · 588; PrintWriter · 591,
I 599, 913; pushBack( ) · 654;
PushbackInputStream · 586;
I/O: and threads, blocking · 860; PushBackReader · 591;
available( ) · 598; blocking on I/O · 869; RandomAccessFile · 592, 593, 599;
blocking, and available( ) · 598; read( ) · 581; readChar( ) · 600;
BufferedInputStream · 586; readDouble( ) · 600; Reader · 581, 589,
BufferedOutputStream · 588; 590, 913; readExternal( ) · 620;
BufferedReader · 563, 591, 597; readLine( ) · 565, 591, 599, 600, 603;
BufferedWriter · 591, 599; readObject( ) · 614; redirecting standard
ByteArrayInputStream · 582; I/O · 604; renameTo( ) · 580; reset( ) ·
ByteArrayOutputStream · 583; 593; seek( ) · 593, 601;
characteristics of files · 578; SequenceInputStream · 582, 592;
CharArrayReader · 590; Serializable · 620; setErr(PrintStream) ·
CharArrayWriter · 590; 604; setIn(InputStream) · 604;
1106
setOut(PrintStream) · 604; vs composition · 642; vs. composition ·
StreamTokenizer · 591, 639, 653, 682; 294
StringBuffer · 582; initial capacity, of a HashMap or HashSet ·
StringBufferInputStream · 582; 491
StringReader · 590, 597; StringWriter · initialization: and class loading · 304;
590; System.err · 602; System.in · 597, array initialization · 231; base class ·
602; System.out · 602; transient · 624; 278; class member · 273; constructor
typical I/O configurations · 594; initialization during inheritance and
Unicode · 590; write( ) · 581; composition · 281; initializing class
writeBytes( ) · 600; writeChars( ) · 600; members at point of definition · 221;
writeDouble( ) · 600; writeExternal( ) · initializing with the constructor · 191;
620; writeObject( ) · 614; Writer · 581, instance initialization · 229, 375;
589, 590, 913; ZipEntry · 610; member initializers · 332; non-static
ZipInputStream · 606; instance initialization · 229; of class
ZipOutputStream · 606 data members · 220; of method
Icon · 738 variables · 220; order of initialization ·
IDL · 982 223, 338; static · 306; with inheritance ·
idltojava · 984 304
if-else statement · 151, 171 inizialization: lazy · 273
IllegalMonitorStateException · 866 inline method calls · 299
ImageIcon · 738 inner class · 365, 799; access rights · 376;
immutable objects · 1047 and super · 385; and overriding · 385;
implementation · 34; and interface · 288, and control frameworks · 394; and
350; and interface, separating · 36; and Swing · 722; and upcasting · 368;
interface, separation · 262; hiding · 35, anonymous · 709; anonymous inner
261, 368; separation of interface and class · 576, 875; anonymous inner class
implementation · 722 and constructors · 375; anonymous, and
implements keyword · 350 table-driven code · 502; callback · 391;
import keyword · 244 closure · 391; hidden reference to the
increment operator · 139 object of the enclosing class · 378;
incremental development · 291 identifiers and .class files · 387; in
indexed property · 818 methods & scopes · 370; inheriting from
indexing operator [ ] · 231 inner classes · 384; nesting within any
indexOf( ): String · 576, 681 arbitrary scope · 372; private · 833;
InflaterInputStream · 606 private inner classes · 397; referring to
inheritance · 38, 260, 271, 275, 311; and the outer class object · 381; static inner
cloning · 1034; and final · 302; and classes · 379
finalize( ) · 333; and synchronized · 858; input: console input · 597
choosing composition vs. inheritance · InputStream · 581, 913
288; class inheritance diagrams · 293; InputStreamReader · 589, 590, 913
combining composition & inheritance · insertNodeInto( ) · 784
281; designing with inheritance · 339; instance: instance initialization · 375; non-
diagram · 47; extending a class during · static instance initialization · 229
41; extending interfaces with instance of a class · 31
inheritance · 358; from an abstract class instanceof: dynamic instanceof · 671;
· 326; from inner classes · 384; keyword · 666
inheritance and method overloading vs. Integer: parseInt( ) · 776
overriding · 286; initialization with Integer wrapper class · 233
inheritance · 304; multiple inheritance interface: and implementation, separation
in C++ and Java · 354; pure inheritance · 262; and inheritance · 358; base-class
vs. extension · 341; specialization · 289; interface · 320; Cloneable interface used
as a flag · 1022; common interface · 325;
1107
defining the class · 88; for an object · library · 440; public Java seminars · 11;
32; graphical user interface (GUI) · 394, versions · 22
689; implementation, separation of · 36; Java 1.1: I/O streams · 589
initializing fields in interfaces · 361; Java AWT · 689
keyword · 349; nesting interfaces within Java Foundation Classes (JFC/Swing) ·
classes and other interfaces · 362; 689
private, as nested interfaces · 364; Java operators · 133
Runnable · 836; separation of interface Java programs, running from the
and implementation · 722; upcasting to Windows Explorer · 705
an interface · 353; user · 78; vs. abstract Java Server Pages (JSP) · 960
· 356; vs. implemenation · 288 Java Virtual Machine · 662
Interface Definition Language (IDL) · 982 JavaBeans: see Beans · 800
interfaces: name collisions when javac · 121
combining interfaces · 356 javah · 1067
interfacing with hardware devices · 1065 JButton · 738
internationalization, in I/O library · 590 JButton, Swing · 706
Internet: Internet Protocol · 905; Internet JCheckBox · 738, 748
Service Provider (ISP) · 699 JCheckboxMenuItem · 765
interrupt( ) · 873 JCheckBoxMenuItem · 760
InterruptedException · 827 JComboBox · 751
intranet · 693; and applets · 693 JComponent · 740, 768
Introspector · 805 JDBC: createStatement( ) · 930; database
IP (Internet Protocol) · 905 URL · 928; DatabaseMetaData · 938;
is-a · 341; relationship, inheritance · 289; executeQuery( ) · 930; flat-file database
relationship, inheritance & upcasting · · 932; getFloat( ) · 930; getInt( ) · 930;
292; vs. is-like-a relationships · 42 getString( ) · 930; Java DataBase
isDaemon( ) · 840 Connectivity · 927; join · 932; relational
isDataFlavorSupported( ) · 792 database · 933; ResultSet · 930; SQL
isInstance · 671 stored procedures · 935; Statement ·
isInterface( ) · 677 930; Structured Query Language (SQL)
is-like-a · 342 · 927
ISP (Internet Service Provider) · 699 JDialog · 771; menus · 759
iteration, in program development · 84 JDK: downloading and installing · 121
iterator · 456 JFC: Java Foundation Classes
Iterator · 456, 463, 500; hasNext( ) · 457; (JFC/Swing) · 689
next( ) · 457 JFileChooser · 776
iterator( ) · 463 JFrame · 704, 713; menus · 759
Jini · 1003
JIT: Just-In Time compilers · 98
J JLabel · 695, 743
JList · 753
JMenu · 759, 765
Jacobsen, Ivar · 1093
JMenuBar · 759, 766
JApplet · 713; menus · 759
JMenuItem · 738, 759, 765, 766, 768
JAR · 816; archive tag, for HTML and JAR
JNI functions · 1069
files · 793; file · 245; jar files and
JNICALL · 1068
classpath · 249; packaging applets to
JNIEnv · 1069
optimize loading · 793
JNIEXPORT · 1068
JAR utility · 611
join · 932
Java · 99; and pointers · 1013; and set-top
JOptionPane · 756
boxes · 146; capitalization style source-
JPanel · 713, 736, 768, 894
code checking tool · 645; compiling and
JPopupMenu · 766
running a program · 121; containers
1108
JProgressBar · 781 list boxes · 753
JRadioButton · 738, 750 listener adapters · 729
JScrollPane · 712, 744, 755, 784 listener classes · 799
JSlider · 781 listener interfaces · 728
JSP · 960 listeners and events · 723
JTabbedPane · 755 Lister, Timothy · 1095
JTable · 784 ListIterator · 467
JTextArea · 711, 790 literal: class literal · 664, 669; double · 156;
JTextField · 708, 740 float · 156; long · 156; values · 155
JTextPane · 747 load factor, of a HashMap or HashSet · 491
JToggleButton · 736 loading: .class files · 247; initialization &
JTree · 781 class loading · 304; loding a class · 305
JVM (Java Virtual Machine) · 662 local loopback IP address · 907
localhost · 907; and RMI · 977
lock, for multithreading · 848
K logarithms: natural logarithms · 156
logical: AND · 154; operator and short-
circuiting · 144; operators · 143; OR ·
keyboard navigation, and Swing · 691
154
keyboard shortcuts · 765
long, literal value marker (L) · 156
keySet( ) · 511
Look & Feel: Pluggable · 787
keywords: class · 32, 38
lvalue · 134
Koenig, Andrew · 1079
L M
main( ) · 277
label · 178
maintenance, program · 85
labeled break · 178
management obstacles · 95
labeled continue · 178
manifest file, for JAR files · 611, 816
late binding · 45, 311, 316
map · 477
layout: controlling layout with layout
Map · 408, 439, 440, 476, 508
managers · 712
Map.Entry · 486
lazy inizialization · 273
mark( ) · 593
left-shift operator (<<) · 147
Math.random( ) · 480; values produced by
length, array member · 232
· 186
length, for arrays · 409
mathematical operators · 137
less than (<) · 141
max( ) · 512
less than or equal to (<=) · 141
member: member function · 35; object · 37
lexicographic vs. alphabetic sorting · 436
member initializers · 332
library: creator, vs. client programmer ·
memory exhaustion, solution via
243; design · 243; use · 244
References · 495
LIFO · 471
mentoring: and training · 95, 96
lightweight: Swing components · 691
menu: JPopupMenu · 766
lightweight persistence · 613
menus: JDialog, JApplet, JFrame · 759
LineNumberInputStream · 586
message box, in Swing · 756
LineNumberReader · 591
message, sending · 33
linked list · 440
meta-class · 662
LinkedList · 467, 472, 505
method: adding more methods to a design
list: drop-down list · 751
· 268; aliasing during a method call ·
List · 408, 439, 440, 467, 753; sorting and
1014; aliasing during method calls · 136;
searching · 511
behavior of polymorphic methods inside
1109
constructors · 337; distinguishing name, HTML keyword · 839
overloaded methods · 196; final · 316, Naming: bind( ) · 976; rebind( ) · 978;
339; final methods · 299; initialization unbind( ) · 978
of method variables · 220; inline narrowing conversion · 155, 170, 201
method calls · 299; inner classes in native method interface (NMI) in Java 1.0
methods & scopes · 370; lookup tool · · 1065
724; method call binding · 315; natural logarithms · 156
overloading · 194; passing a reference nesting interfaces · 362
into a method · 1014; polymorphic network programming · 904; accept( ) ·
method call · 311; private · 339; 909; client · 907; Common-Gateway
protected methods · 290; recursive · Interface (CGI) · 948; datagrams · 923;
459; static · 206; synchronized method dedicated connection · 917; displaying a
and blocking · 860 Web page from within an applet · 923;
Method · 808; for reflection · 678 DNS (Domain Name System) · 905;
MethodDescriptors · 808 dotted quad · 905; getInputStream( ) ·
methodology, analysis and design · 71 909; getOutputStream( ) · 909; HTML ·
Meyers, Scott · 35 948; identifying machines · 905;
Microsoft · 820; Visual Basic · 800 Internet Protocol (IP) · 905; Java
min( ) · 512 DataBase Connectivity (JDBC) · 927;
mission statement · 75 local loopback IP address · 907;
mistakes, and design · 268 localhost · 907; multithreading · 917;
mkdirs( ) · 580 port · 908; reliable protocol · 923;
mnemonics (keyboard shortcuts) · 765 server · 907; serving multiple clients ·
modulus · 137 917; showDocument( ) · 924; Socket ·
monitor, for multithreading · 848 915; stream-based sockets · 923; testing
multicast · 814; event, and JavaBeans · programs without a network · 907;
854; multicast events · 796 Transmission Control Protocol (TCP) ·
multidimensional arrays · 236 923; unreliable protocol · 923; URL ·
Multimedia CD ROM for book · 20 925; User Datagram Protocol (UDP) ·
multiparadigm programming · 31 923
multiple inheritance, in C++ and Java · new operator · 207; and primitives, array ·
354 233
multiplication · 137 newInstance( ) · 737; reflection · 676
multiply-nested class · 383 next( ), Iterator · 457
MultiStringMap · 652 nextToken( ) · 654
multitasking · 825 NMI: Java 1.0 native method interface ·
multithreading · 825, 917; and containers · 1065
514; and JavaBeans · 854; blocking · no-arg: constructors · 196
859; deadlock · 865; deciding what non-Java code, calling · 1065
methods to synchronize · 858; North, BorderLayout · 713
drawbacks · 899; Runnable · 891; NOT: logical (!) · 143
servlets · 954; when to use it · 899 not equivalent (!=) · 141
multi-tiered systems · 796 notify( ) · 860
notifyAll( ) · 860
notifyListeners( ) · 858
N null · 107, 411; garbage collection, allowing
cleanup · 397
NullPointerException · 550
name · 697; clash · 244; collisions · 250;
numbers, binary · 156
creating unique package names · 247;
spaces · 244
name collisions when combining interfaces
· 356
1110
141; shift · 147; ternary · 151; unary · 139,
O 146
optional methods, in the Java 2 containers
object · 31; aliasing · 136; arrays are first- · 516
class objects · 409; assigning objects by OR · 154; (||) · 143
copying references · 134; assignment order: of constructor calls with inheritance
and reference copying · 134; business · 330; of finalization of objects · 336; of
object/logic · 796; Class object · 633, initialization · 223, 304, 338
662, 848; creation · 192; equals( ) organization, code · 255
method · 142; equivalence · 141; OutputStream · 581, 583, 913
equivalence vs reference equivalence · OutputStreamWriter · 589, 590, 913
142; final · 295; five stages of object overflow: and primitive types · 169
design · 82; guidelines for object overloading: and constructors · 194;
development · 83; immutable objects · distinguishing overloaded methods ·
1047; interface to · 32; lock, for 196; lack of name hiding during
multithreading · 848; member · 37; inheritance · 286; method overloading ·
object-oriented programming · 660; 194; on return values · 202; operator +
order of finalization of objects · 336; and += overloading for String · 277;
process of creation · 227; reference operator overloading · 153; operator
equivalence vs. object equivalence · overloading for String · 1054;
1025; serialization · 613; web of objects · overloading vs. overriding · 286; vs.
614, 1020 overriding · 324
Object · 408; clone( ) · 1021, 1025; overriding: and inner classes · 385;
getClass( ) · 674; hashCode( ) · 477; function · 42; overloading vs. overriding
standard root class, default inheritance · 286; vs. overloading · 324
from · 275; wait( ) and notify( ) methods
· 866
object-oriented: analysis and design · 71;
basic concepts of object-oriented
P
programming (OOP) · 29
ObjectOutputStream · 614 package · 244; access, and friendly · 255;
obstacles, management · 95 and applets · 699; and directory
Octal · 156 structure · 254; creating unique package
ODBC · 928 names · 247; default package · 257;
OMG · 981 names, capitalization · 116; visibility,
ones complement operator · 146 friendly · 368
OOP · 262; analysis and design · 71; basic paintComponent( ) · 768, 776
characteristics · 31; basic concepts of Painting on a JPanel in Swing · 768
object-oriented programming · 29; pair programming · 90
protocol · 350; Simula programming paralysis, analysis · 72
language · 32; substitutability · 31 param, HTML keyword · 839
operator · 133; + and += overloading for parameter, applet · 697
String · 277; +, for String · 1054; == and parameterized type · 455
!= · 1025; binary · 146; bitwise · 146; parseInt( ) · 776
casting · 154; comma · 152; comma pass: pass by value · 1018; passing a
operator · 175; common pitfalls · 153; reference into a method · 1014
indexing operator [ ] · 231; logical · 143; Pattern: Command Pattern · 575
logical operators and short-circuiting · patterns, design · 86, 94
144; ones-complement · 146; operator patterns, design patterns · 266
overloading for String · 1054; perfect hashing function · 488
overloading · 153; precedence · 134; performance: and final · 302
precedence mnemonic · 158; relational · performance issues · 96
Perl programming language · 705
1111
persistence · 630; lightweight persistence · programming: basic concepts of object-
613 oriented programming (OOP) · 29;
PhantomReference · 495 coding standards · 1077; event-driven
pipe · 581 programming · 707; Extreme
piped stream · 869 Programming (XP) · 88, 1093; in the
piped streams · 602 large · 92; multiparadigm · 31; object-
PipedInputStream · 582 oriented · 660; pair · 90
PipedOutputStream · 582, 583 progress bar · 780
PipedReader · 590 promotion: of primitive types · 169; type
PipedWriter · 590 promotion · 157
planning, software development · 74 Properties · 652
Plauger, P.J. · 1094 property · 801; bound properties · 818;
Pluggable Look & Feel · 787 constrained properties · 818; custom
pointer: Java exclusion of pointers · 391 property editor · 818; custom property
pointers, and Java · 1013 sheet · 818; indexed property · 818
polymorphism · 44, 311, 346, 660, 685; PropertyChangeEvent · 818
and constructors · 330; behavior of PropertyDescriptors · 808
polymorphic methods inside ProptertyVetoException · 818
constructors · 337 protected · 36, 243, 255, 260, 290; and
port · 908 friendly · 290; is also friendly · 261;
portability in C, C++ and Java · 158 more accessible than friendly · 335; use
position, absolute, when laying out Swing in clone( ) · 1021
components · 716 protocol · 350; unreliable protocol · 923
precedence: operator precedence prototyping: rapid · 86
mnemonic · 158 public · 36, 243, 255, 256; and interface ·
prerequisites, for this book · 29 350; class, and compilation units · 245
primitive: comparison · 142; containers of pure: substitution · 42
primitives · 412; data types, and use pure inheritance, vs. extension · 341
with operators · 159; dealing with the pure substitution · 342
immutability of primitive wrapper pushBack( ) · 654
classes · 1047; final · 294; final static PushbackInputStream · 586
primitives · 296; initialization of class PushBackReader · 591
data members · 220; wrappers · 481 put( ), HashMap · 481
primitive types · 105 Python · 81, 99
printInfo( ) · 677
printing arrays · 417
println( ) · 458 Q
printStackTrace( ) · 543, 545
PrintStream · 588
queue · 440, 472
PrintWriter · 591, 599, 913
priority: default priority for a Thread
group · 882; thread · 877
private · 36, 243, 255, 258, 290, 848; R
illusion of overriding private methods ·
299; inner class · 833; inner classes · RAD (Rapid Application Development) ·
397; interfaces, when nested · 364; 678
methods · 339 radio button · 750
problem space · 30, 291 random number generator, values
process, and threading · 825 produced by · 186
program: maintenance · 85 random( ) · 480
programmer, client · 35 RandomAccessFile · 592, 593, 599
rapid prototyping · 86
1112
reachable objects and garbage collection · RMI: AlreadyBoundException · 978; and
495 CORBA · 989; bind( ) · 976; localhost ·
read( ) · 581 977; rebind( ) · 978; Remote · 974;
readChar( ) · 600 remote interface · 974; Remote Method
readDouble( ) · 600 Invocation · 973; remote object registry ·
Reader · 581, 589, 590, 869, 913 976; RemoteException · 974, 980; rmic ·
readExternal( ) · 620 979; rmic and classpath · 979;
reading from standard input · 602 rmiregistry · 976; RMISecurityManager
readLine( ) · 565, 591, 599, 600, 603 · 976; Serializable arguments · 978;
readObject( ) · 614; with Serializable · 627 skeleton · 978; stub · 978; TCP/IP · 977;
rebind( ) · 978 unbind( ) · 978; UnicastRemoteObject ·
recursion, unintended via toString() · 459 974
redirecting standard I/O · 604 rmic · 979
refactoring · 85 rmiregistry · 976
reference: assigning objects by copying RMISecurityManager · 976
references · 134; equivalence vs object rollover · 740
equivalence · 142; final · 295; finding RTTI: and cloning · 1025; cast · 661; Class ·
exact type of a base reference · 662; null 737; Class object · 662;
· 107; reference equivalence vs. object ClassCastException · 666; Constructor ·
equivalence · 1025 678; difference between RTTI and
Reference, from java.lang.ref · 495 reflection · 679; downcast · 666; Field ·
referencing, forward referencing · 222 678; getConstructor( ) · 737; instanceof
reflection · 677, 678, 724, 804; and Beans · keyword · 666; isInstance · 671; meta-
801; difference between RTTI and class · 662; Method · 678;
reflection · 679 newInstance( ) · 737; reflection · 677;
reflection example · 736 run-time type identification (RTTI) ·
registry: remote object registry · 976 344; type-safe downcast · 665; using the
relational: database · 933; operators · 141 Class object · 674
reliable protocol · 923 Rumbaugh, James · 1093
Remote Method Invocation (RMI) · 973 runFinalizersOnExit( ) · 335
RemoteException · 980 Runnable · 891; interface · 836; Thread ·
removeActionListener( ) · 812, 857 859
removeXXXListener( ) · 723 running a Java program · 121
renameTo( ) · 580 run-time binding · 316; polymorphism ·
reporting errors in book · 23 311
request, in OOP · 33 run-time type identification: (RTTI) · 344;
requirements analysis · 75 misuse · 685; shape example · 659;
reset( ) · 593 when to use it · 685
ResultSet · 930 RuntimeException · 408, 550
resume( ) · 860, 864; and deadlocks · 873; rvalue · 134
deprecation in Java 2 · 875
resumption, termination vs. resumption,
exception handling · 536 S
re-throwing an exception · 545
return: an array · 413; constructor return
safety, and applet restrictions · 692
value · 193; overloading on return value
scenario · 77
· 202
scheduling · 79
reusability · 37
scope: inner class nesting within any
reuse · 83; code reuse · 271; existing class
arbitrary scope · 372; inner classes in
libraries · 94; reusable code · 800
methods & scopes · 370; use case · 84
right-shift operator (>>) · 147
scrolling in Swing · 712
searching: sorting and searching Lists · 511
1113
searching an array · 437 signed two’s complement · 151
section, critical section and synchronized Simula programming language · 32
block · 852 Simula-67 · 262
seek( ) · 593, 601 sine wave · 768
seminars: public Java seminars · 11; singleton: design pattern · 266
training, provided by Bruce Eckel · 23 size( ), ArrayList · 451
sending a message · 33 Size, of a HashMap or HashSet · 491
separating business logic from UI logic · sizeof( ): lack of in Java · 158
796 skeleton, RMI · 978
separation of interface and sleep( ) · 827, 846, 860, 862
implementation · 36, 262, 722 slider · 780
SequenceInputStream · 582, 592 Smalltalk · 31, 207
Serializable · 613, 620, 625, 637, 814; Socket · 915
readObject( ) · 627; writeObject( ) · 627 sockets, stream-based · 923
serialization: and object storage · 630; and SoftReference · 495
transient · 624; controlling the process software: development methodology · 72
of serialization · 619; Software Development Conference · 10
defaultReadObject( ) · 629; solution space · 30
defaultWriteObject( ) · 629; RMI sorting · 431; and searching Lists · 511
arguments · 978; to perform deep source code copyright notice · 20
copying · 1032; Versioning · 630 South, BorderLayout · 713
server · 907 space: problem · 30; solution · 30
servlet · 948; multithreading · 954; specialization · 289
running servlets with Tomcat · 960; specification: system specification · 75
session tracking · 955 specification, exception · 542
session: and JSP · 969 specifier: access specifiers · 36, 243, 255
session tracking, with servlets · 955 SQL: stored procedures · 935; Structured
Set · 408, 439, 440, 473, 506 Query Language · 927
setActionCommand( ) · 765 Stack · 471, 521
setBorder( ) · 743 standard input: Reading from standard
setContents( ) · 792 input · 602
setDaemon( ) · 840 standards: coding standards · 22, 1077
setDefaultCloseOperation( ) · 704 startup costs · 95
setErr(PrintStream) · 604 stateChanged( ) · 771
setIcon( ) · 740 statement: mission · 75
setIn(InputStream) · 604 Statement · 930
setLayout( ) · 713 static · 350; and final · 294; block · 228;
setMnemonic( ) · 765 clause · 664; construction clause · 228;
setOut(PrintStream) · 604 data initialization · 225; final static
setPriority( ) · 878 primitives · 296; initialization · 306;
setToolTipText( ) · 740 inner classes · 379; keyword · 206;
shallow copy · 1019, 1027 method · 206; synchronized static · 848
shape: example · 39, 316; example, and STL: C++ · 440
run-time type identification · 659 stop( ): and deadlocks · 873; deprecation
shift operators · 147 in Java 2 · 873
short-circuit, and logical operators · 144 stored procedures in SQL · 935
shortcut, keyboard · 765 stream, I/O · 581
show( ) · 773 stream-based sockets · 923
showDocument( ) · 924 StreamTokenizer · 591, 639, 653, 682
shuffle( ) · 512 String: automatic type conversion · 454;
side effect · 133, 141, 202, 1016 class methods · 1052; concatenation
sign extension · 147 with operator + · 153; immutability ·
1114
1052; indexOf( ) · 576, 681; table-driven code, and anonymous inner
lexicographic vs. alphabetic sorting · classes · 502
436; methods · 1056; operator + · 454; TCP, Transmission Control Protocol · 923
Operator + · 153; operator + and += TCP/IP, and RMI · 977
overloading · 277; toString( ) · 272, 452 template: in C++ · 455
StringBuffer · 582; methods · 1058 termination vs. resumption, exception
StringBufferInputStream · 582 handling · 536
StringReader · 590, 597 ternary operator · 151
StringSelection · 792 testing: automated · 89; Extreme
StringTokenizer · 642 Programming (XP) · 88; unit testing ·
StringWriter · 590 277
struts, in BoxLayout · 717 testing techniques · 381
stub, RMI · 978 this keyword · 203
style of creating classes · 262 Thread · 825, 827; and JavaBeans · 854;
subobject · 278, 288 and Runnable · 891; blocked · 859;
substitutability, in OOP · 31 combined with main class · 834;
substitution: principle · 42 daemon threads · 840; dead · 859;
subtraction · 137 deadlock · 872; deciding what methods
super · 280; and finalize( ) · 335; and inner to synchronize · 858; destroy( ) · 877;
classes · 385 drawbacks · 899; getPriority( ) · 878;
super keyword · 278 I/O and threads, blocking · 860;
super.clone( ) · 1021, 1025, 1041 interrupt( ) · 873; isDaemon( ) · 840;
superclass · 278 new Thread · 859; notify( ) · 860;
suspend( ) · 860, 864; and deadlocks · 873; notifyAll( ) · 860; order of execution of
deprecation in Java 2 · 875 threads · 831; priority · 877; properly
Swing · 689 suspending & resuming · 874; resume( )
Swing component examples · 734 · 860, 864; resume( ) , deprecation in
Swing components, using HTML with · Java 2 · 875; resume( ), and deadlocks ·
779 873; run( ) · 829; Runnable · 859;
Swing event model · 722, 794 Runnable interface · 836; setDaemon( )
switch keyword · 183 · 840; setPriority( ) · 878; sharing
synchronized · 59, 848; and inheritance · limited resources · 842; sleep( ) · 846,
858; and wait( ) & notify( ) · 866; 860, 862; start( ) · 830; states · 859;
containers · 514; deciding what methods stop( ) , deprecation in Java 2 · 873;
to synchronize · 858; efficiency · 853; stop( ), and deadlocks · 873; stopping ·
method, and blocking · 860; static · 873; suspend( ) · 860, 864; suspend( ) ,
848; synchronized block · 852 deprecation in Java 2 · 875; suspend( ),
system clipboard · 790 and deadlocks · 873; synchronized
system specification · 75 method and blocking · 860; thread
System.arraycopy( ) · 429 group · 882; thread group, default
System.err · 538, 602 priority · 882; threads and efficiency ·
System.gc( ) · 213 828; wait( ) · 860, 866; when they can
System.in · 597, 602 be suspended · 847; when to use threads
System.out · 602 · 899; yield( ) · 860
System.out.println( ) · 458 throw keyword · 534
System.runFinalization( ) · 213 Throwable · 547; base class for Exception ·
543
throwing an exception · 533
T time-critical code sections · 1065
toArray( ) · 511
token · 639
tabbed dialog · 755
Tokenizing · 639
table · 784
1115
Tomcat, standard servlet container · 960 updates of the book · 22
tool tips · 740 URL · 925
TooManyListenersException · 796, 814 use case · 76; iteration · 84; scope · 84
toString( ) · 272, 452, 458, 500 User Datagram Protocol (UDP) · 923
training · 93; and mentoring · 95, 96 user interface · 78; and threads, for
training seminars provided by Bruce Eckel responsiveness · 831; responsive, with
· 23 threading · 826
Transferable · 792
transient keyword · 624
translation unit · 245 V
Transmission Control Protocol (TCP) · 923
tree · 781
value: preventing change at run-time · 294
TreeMap · 476, 510, 642
value, HTML keyword · 839
TreeSet · 473, 506
variable: defining a variable · 174;
true · 143
initialization of method variables · 220;
try · 286, 554; try block in exceptions · 535
variable argument lists (unknown
two’s complement, signed · 151
quantity and type of arguments) · 235
type: base · 39; data type equivalence to
vector: of change · 86
class · 33; derived · 39; finding exact
Vector · 505, 519, 521
type of a base reference · 662;
vector of change · 397
parameterized type · 455; primitive ·
versioning, serialization · 630
105; primitive data types and use with
versions of Java · 22
operators · 159; type checking and
visibility, package visibility, (friendly) ·
arrays · 408; type safety in Java · 154;
368
type-safe downcast in run-time type
visual: programming · 800
identification · 665; weak typing · 45
Visual Basic, Microsoft · 800
TYPE field, for primitive class literals · 665
visual programming environments · 690
type safe sets of constants · 361
type-conscious ArrayList · 454
W
U
wait( ) · 860, 866
Waldrop, M. Mitchell · 1095
UDP, User Datagram Protocol · 923
weak: weakly typed language · 45
UML · 81; indicating composition · 37;
WeakHashMap · 498
Unified Modeling Language · 35, 1093
WeakReference · 495
unary: minus (-) · 139; operator · 146;
Web: displaying a Web page from within
operators · 139; plus (+) · 139
an applet · 923; placing an applet inside
unbind( ) · 978
a Web page · 695; safety, and applet
unicast · 814; unicast events · 796
restrictions · 692
UnicastRemoteObject · 974
web of objects · 614, 1020
Unicode · 590
West, BorderLayout · 713
Unified Modeling Language (UML) · 35,
while · 172
1093
widening conversion · 155
unit testing · 277
wild-card · 73
unmodifiable, making a Collection or Map
WindowAdapter · 704
unmodifiable · 513
windowClosing( ) · 704, 771
unsupported methods, in the Java 2
windowed applications · 700
containers · 516
Windows Explorer, running Java
UnsupportedOperationException · 516
programs from · 705
upcasting · 47, 291, 312, 660; and interface
· 353; inner classes and upcasting · 368
1116
wrapper, dealing with the immutability of
primitive wrapper classes · 1047
Y
write( ) · 581
writeBytes( ) · 600 yield( ) · 860
writeChars( ) · 600
writeDouble( ) · 600
writeExternal( ) · 620 Z
writeObject( ) · 614; with Serializable · 627
Writer · 581, 589, 590, 869, 913 zero extension · 147
ZipEntry · 610
ZipInputStream · 606
X ZipOutputStream · 606
XOR · 146
XP, Extreme Programming · 88
1117
Check www.BruceEckel.com
for in-depth details
and the date and location
of the next
Hands-On Java Seminar
• Based on this book
• Taught by Bruce Eckel
• Personal attention from Bruce Eckel
and his seminar assistants
• Includes in-class programming exercises
• Intermediate/Advanced seminars also offered
• Hundreds have already enjoyed this seminar—
see the Web site for their testimonials
1118
Bruce Eckel’s Hands-On Java Seminar
Multimedia CD
It’s like coming to the seminar!
Available at www.BruceEckel.com
! The Hands-On Java Seminar captured on a Multimedia CD!
! Overhead slides and synchronized audio voice narration for all
the lectures. Just play it to see and hear the lectures!
! Created and narrated by Bruce Eckel.
! Based on the material in this book.
! Demo lecture available at www.BruceEckel.com
1119
End-User License Agreement for Microsoft Software
1120
student end users at your educational institution, provided that all such end
users comply with all other terms of this EULA, OR
(B) Per License Model. If you have multiple licenses for the SOFTWARE
PRODUCT, then at any time you may have as many copies of the SOFTWARE
PRODUCT in use as you have licenses, provided that such use is limited to
student or faculty end users at your educational institution and provided that
all such end users comply with all other terms of this EULA. For purposes of
this subsection, the SOFTWARE PRODUCT is "in use" on a computer when it is
loaded into the temporary memory (i.e., RAM) or installed into the permanent
memory (e.g., hard disk, CD ROM, or other storage device) of that computer,
except that a copy installed on a network server for the sole purpose of
distribution to other computers is not "in use". If the anticipated number of
users of the SOFTWARE PRODUCT will exceed the number of applicable
licenses, then you must have a reasonable mechanism or process in place to
ensure that the number of persons using the SOFTWARE PRODUCT
concurrently does not exceed the number of licenses.
2. DESCRIPTION OF OTHER RIGHTS AND LIMITATIONS.
• Limitations on Reverse Engineering, Decompilation, and Disassembly. You
may not reverse engineer, decompile, or disassemble the SOFTWARE
PRODUCT, except and only to the extent that such activity is expressly
permitted by applicable law notwithstanding this limitation.
• Separation of Components. The SOFTWARE PRODUCT is licensed as a single
product. Its component parts may not be separated for use on more than one
computer.
• Rental. You may not rent, lease or lend the SOFTWARE PRODUCT.
• Trademarks. This EULA does not grant you any rights in connection with any
trademarks or service marks of Microsoft.
• Software Transfer. The initial user of the SOFTWARE PRODUCT may make a
one-time permanent transfer of this EULA and SOFTWARE PRODUCT only
directly to an end user. This transfer must include all of the SOFTWARE
PRODUCT (including all component parts, the media and printed materials, any
upgrades, this EULA, and, if applicable, the Certificate of Authenticity). Such
transfer may not be by way of consignment or any other indirect transfer. The
transferee of such one-time transfer must agree to comply with the terms of
this EULA, including the obligation not to further transfer this EULA and
SOFTWARE PRODUCT.
• No Support. Microsoft shall have no obligation to provide any product
support for the SOFTWARE PRODUCT.
• Termination. Without prejudice to any other rights, Microsoft may terminate
this EULA if you fail to comply with the terms and conditions of this EULA. In
1121
such event, you must destroy all copies of the SOFTWARE PRODUCT and all of
its component parts.
3. COPYRIGHT. All title and intellectual property rights in and to the
SOFTWARE PRODUCT (including but not limited to any images, photographs,
animations, video, audio, music, text, and "applets" incorporated into the
SOFTWARE PRODUCT), the accompanying printed materials, and any copies of
the SOFTWARE PRODUCT are owned by Microsoft or its suppliers. All title and
intellectual property rights in and to the content which may be accessed
through use of the SOFTWARE PRODUCT is the property of the respective
content owner and may be protected by applicable copyright or other
intellectual property laws and treaties. This EULA grants you no rights to use
such content. All rights not expressly granted are reserved by Microsoft.
4. BACKUP COPY. After installation of one copy of the SOFTWARE PRODUCT
pursuant to this EULA, you may keep the original media on which the
SOFTWARE PRODUCT was provided by Microsoft solely for backup or archival
purposes. If the original media is required to use the SOFTWARE PRODUCT on
the COMPUTER, you may make one copy of the SOFTWARE PRODUCT solely
for backup or archival purposes. Except as expressly provided in this EULA,
you may not otherwise make copies of the SOFTWARE PRODUCT or the printed
materials accompanying the SOFTWARE PRODUCT.
5. U.S. GOVERNMENT RESTRICTED RIGHTS. The SOFTWARE PRODUCT and
documentation are provided with RESTRICTED RIGHTS. Use, duplication, or
disclosure by the Government is subject to restrictions as set forth in
subparagraph (c)(1)(ii) of the Rights in Technical Data and Computer Software
clause at DFARS 252.227-7013 or subparagraphs (c)(1) and (2) of the
Commercial Computer Software-Restricted Rights at 48 CFR 52.227-19, as
applicable. Manufacturer is Microsoft Corporation/One Microsoft
Way/Redmond, WA 98052-6399.
6. EXPORT RESTRICTIONS. You agree that you will not export or re-export the
SOFTWARE PRODUCT, any part thereof, or any process or service that is the
direct product of the SOFTWARE PRODUCT (the foregoing collectively referred
to as the "Restricted Components"), to any country, person, entity or end user
subject to U.S. export restrictions. You specifically agree not to export or re-
export any of the Restricted Components (i) to any country to which the U.S.
has embargoed or restricted the export of goods or services, which currently
include, but are not necessarily limited to Cuba, Iran, Iraq, Libya, North Korea,
Sudan and Syria, or to any national of any such country, wherever located,
who intends to transmit or transport the Restricted Components back to such
country; (ii) to any end-user who you know or have reason to know will utilize
the Restricted Components in the design, development or production of
nuclear, chemical or biological weapons; or (iii) to any end-user who has been
1122
prohibited from participating in U.S. export transactions by any federal agency
of the U.S. government. You warrant and represent that neither the BXA nor
any other U.S. federal agency has suspended, revoked, or denied your export
privileges.
7. NOTE ON JAVA SUPPORT. THE SOFTWARE PRODUCT MAY CONTAIN
SUPPORT FOR PROGRAMS WRITTEN IN JAVA. JAVA TECHNOLOGY IS NOT
FAULT TOLERANT AND IS NOT DESIGNED, MANUFACTURED, OR INTENDED
FOR USE OR RESALE AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS
ENVIRONMENTS REQUIRING FAIL-SAFE PERFORMANCE, SUCH AS IN THE
OPERATION OF NUCLEAR FACILITIES, AIRCRAFT NAVIGATION OR
COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE SUPPORT
MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF JAVA
TECHNOLOGY COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR
SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE.
MISCELLANEOUS
If you acquired this product in the United States, this EULA is governed by the
laws of the State of Washington.
If you acquired this product in Canada, this EULA is governed by the laws of
the Province of Ontario, Canada. Each of the parties hereto irrevocably attorns
to the jurisdiction of the courts of the Province of Ontario and further agrees to
commence any litigation which may arise hereunder in the courts located in
the Judicial District of York, Province of Ontario.
If this product was acquired outside the United States, then local law may
apply.
Should you have any questions concerning this EULA, or if you desire to
contact Microsoft for any reason, please contact
Microsoft, or write: Microsoft Sales Information Center/One Microsoft
Way/Redmond, WA 98052-6399.
LIMITED WARRANTY
LIMITED WARRANTY. Microsoft warrants that (a) the SOFTWARE PRODUCT will
perform substantially in accordance with the accompanying written materials
for a period of ninety (90) days from the date of receipt, and (b) any Support
Services provided by Microsoft shall be substantially as described in applicable
written materials provided to you by Microsoft, and Microsoft support
engineers will make commercially reasonable efforts to solve any problem. To
the extent allowed by applicable law, implied warranties on the SOFTWARE
PRODUCT, if any, are limited to ninety (90) days. Some states/jurisdictions do
1123
not allow limitations on duration of an implied warranty, so the above
limitation may not apply to you.
CUSTOMER REMEDIES. Microsoft's and its suppliers' entire liability and your
exclusive remedy shall be, at Microsoft's option, either (a) return of the price
paid, if any, or (b) repair or replacement of the SOFTWARE PRODUCT that
does not meet Microsoft's Limited Warranty and that is returned to Microsoft
with a copy of your receipt. This Limited Warranty is void if failure of the
SOFTWARE PRODUCT has resulted from accident, abuse, or misapplication.
Any replacement SOFTWARE PRODUCT will be warranted for the remainder of
the original warranty period or thirty (30) days, whichever is longer. Outside
the United States, neither these remedies nor any product support services
offered by Microsoft are available without proof of purchase from an authorized
international source.
NO OTHER WARRANTIES. TO THE MAXIMUM EXTENT PERMITTED BY
APPLICABLE LAW, MICROSOFT AND ITS SUPPLIERS DISCLAIM ALL OTHER
WARRANTIES AND CONDITIONS, EITHER EXPRESS OR IMPLIED, INCLUDING,
BUT NOT LIMITED TO, IMPLIED WARRANTIES OR CONDITIONS OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-
INFRINGEMENT, WITH REGARD TO THE SOFTWARE PRODUCT, AND THE
PROVISION OF OR FAILURE TO PROVIDE SUPPORT SERVICES. THIS LIMITED
WARRANTY GIVES YOU SPECIFIC LEGAL RIGHTS. YOU MAY HAVE OTHERS,
WHICH VARY FROM STATE/JURISDICTION TO STATE/JURISDICTION.
LIMITATION OF LIABILITY. TO THE MAXIMUM EXTENT PERMITTED BY
APPLICABLE LAW, IN NO EVENT SHALL MICROSOFT OR ITS SUPPLIERS BE
LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL
DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR
LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS
INFORMATION, OR ANY OTHER PECUNIARY LOSS) ARISING OUT OF THE USE
OF OR INABILITY TO USE THE SOFTWARE PRODUCT OR THE FAILURE TO
PROVIDE SUPPORT SERVICES, EVEN IF MICROSOFT HAS BEEN ADVISED OF
THE POSSIBILITY OF SUCH DAMAGES. IN ANY CASE, MICROSOFT'S ENTIRE
LIABILITY UNDER ANY PROVISION OF THIS EULA SHALL BE LIMITED TO THE
GREATER OF THE AMOUNT ACTUALLY PAID BY YOU FOR THE SOFTWARE
PRODUCT OR U.S.$5.00; PROVIDED, HOWEVER, IF YOU HAVE ENTERED INTO
A MICROSOFT SUPPORT SERVICES AGREEMENT, MICROSOFT'S ENTIRE
LIABILITY REGARDING SUPPORT SERVICES SHALL BE GOVERNED BY THE
TERMS OF THAT AGREEMENT. BECAUSE SOME STATES/JURISDICTIONS DO
NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY, THE ABOVE
LIMITATION MAY NOT APPLY TO YOU.
0495 Part No. 64358
1124
LICENSE AGREEMENT FOR MindView, Inc.'s
Thinking in C: Foundations for Java & C++ CD ROM
by Chuck Allison
This CD is provided together with the book "Thinking in Java, 2nd edition."
SOFTWARE REQUIREMENTS
The purpose of this CD is to provide the Content, not the associated software
necessary to view the Content. The Content of this CD is in HTML for viewing
with Microsoft Internet Explorer 4 or newer, and uses Microsoft Sound Codecs
available in Microsoft's Windows Media Player for Windows. It is your
responsibility to correctly install the appropriate Microsoft software for your
system.
The text, images, and other media included on this CD ("Content") and their
compilation are licensed to you subject to the terms and conditions of this
Agreement by MindView, Inc., having a place of business at 5343 Valle Vista,
La Mesa, CA 91941. Your rights to use other programs and materials included
on the CD are also governed by separate agreements distributed with those
programs and materials on the CD (the "Other Agreements"). In the event of
any inconsistency between this Agreement and the Other Agreements, this
Agreement shall govern. By using this CD, you agree to be bound by the terms
and conditions of this Agreement. MindView, Inc. owns title to the Content and
to all intellectual property rights therein, except insofar as it contains materials
that are proprietary to third-party suppliers. All rights in the Content except
those expressly granted to you in this Agreement are reserved to MindView,
Inc. and such suppliers as their respective interests may appear.
1. LIMITED LICENSE
MindView, Inc. grants you a limited, nonexclusive, nontransferable license to
use the Content on a single dedicated computer (excluding network servers).
This Agreement and your rights hereunder shall automatically terminate if you
fail to comply with any provisions of this Agreement or any of the Other
Agreements. Upon such termination, you agree to destroy the CD and all
copies of the CD, whether lawful or not, that are in your possession or under
your control.
2. ADDITIONAL RESTRICTIONS
a. You shall not (and shall not permit other persons or entities to) directly or
indirectly, by electronic or other means, reproduce (except for archival
purposes as permitted by law), publish, distribute, rent, lease, sell, sublicense,
assign, or otherwise transfer the Content or any part thereof.
b. You shall not (and shall not permit other persons or entities to) use the
Content or any part thereof for any commercial purpose or merge, modify,
create derivative works of, or translate the Content.
c. You shall not (and shall not permit other persons or entities to) obscure
MindView's or its suppliers copyright, trademark, or other proprietary notices
or legends from any portion of the Content or any related materials.
3. PERMISSIONS
a. Except as noted in the Contents of the CD, you must treat this software just
like a book. However, you may copy it onto a computer to be used and you
may make archival copies of the software for the sole purpose of backing up
the software and protecting your investment from loss. By saying, "just like a
book," MindView, Inc. means, for example, that this software may be used by
any number of people and may be freely moved from one computer location to
another, so long as there is no possibility of its being used at one location or
on one computer while it is being used at another. Just as a book cannot be
read by two different people in two different places at the same time, neither
can the software be used by two different people in two different places at the
same time.
b. You may show or demonstrate the un-modified Content in a live
presentation, live seminar, or live performance as long as you attribute all
material of the Content to MindView, Inc.
c. Other permissions and grants of rights for use of the CD must be obtained
directly from MindView, Inc. at http://www.MindView.net. (Bulk copies of the
CD may also be purchased at this site.)
DISCLAIMER OF WARRANTY
The Content and CD are provided "AS IS" without warranty of any kind, either
express or implied, including, without limitation, any warranty of
merchantability and fitness for a particular purpose. The entire risk as to the
results and performance of the CD and Content is assumed by you. MindView,
Inc. and its suppliers assume no responsibility for defects in the CD, the
accuracy of the Content, or omissions in the CD or the Content. MindView, Inc.
and its suppliers do not warrant, guarantee, or make any representations
regarding the use, or the results of the use, of the product in terms of
correctness, accuracy, reliability, currentness, or otherwise, or that the Content
will meet your needs, or that operation of the CD will be uninterrupted or
error-free, or that any defects in the CD or Content will be corrected.
MindView, Inc. and its suppliers shall not be liable for any loss, damages, or
costs arising from the use of the CD or the interpretation of the Content. Some
states do not allow exclusion or limitation of implied warranties or limitation of
liability for incidental or consequential damages, so all of the above limitations
or exclusions may not apply to you.
In no event shall MindView, Inc. or its suppliers' total liability to you for all
damages, losses, and causes of action (whether in contract, tort, or otherwise)
exceed the amount paid by you for the CD.
MindView, Inc., and Prentice-Hall, Inc. specifically disclaim the implied
warrantees of merchantability and fitness for a particular purpose. No oral or
written information or advice given by MindView, Inc., Prentice-Hall, Inc., their
dealers, distributors, agents or employees shall create a warrantee. You may
have other rights, which vary from state to state.
Neither MindView, Inc., Bruce Eckel, Chuck Allison, Prentice-Hall, nor anyone
else who has been involved in the creation, production or delivery of the
product shall be liable for any direct, indirect, consequential, or incidental
damages (including damages for loss of business profits, business interruption,
loss of business information, and the like) arising out of the use of or inability
to use the product even if MindView, Inc., has been advised of the possibility
of such damages. Because some states do not allow the exclusion or limitation
of liability for consequential or incidental damages, the above limitation may
not apply to you.
This CD is provided as a supplement to the book "Thinking in Java 2nd
edition." The sole responsibility of Prentice-Hall will be to provide a
replacement CD in the event that the one that came with the book is
defective. This replacement warrantee shall be in effect for a period of sixty
days from the purchase date. MindView, Inc. does not bear any additional
responsibility for the CD.
NO TECHNICAL SUPPORT IS PROVIDED WITH THIS CD ROM
The following are trademarks of their respective companies in the U.S. and
may be protected as trademarks in other countries: Sun and the Sun Logo,
1127
Sun Microsystems, Java, all Java-based names and logos and the Java Coffee
Cup are trademarks of Sun Microsystems; Internet Explorer, the Windows
Media Player, DOS, Windows 95, and Windows NT are trademarks of Microsoft.
Thinking in C: Foundations for Java & C++
Multimedia Seminar-on-CD ROM
©2000 MindView, Inc. All rights reserved.
WARNING: BEFORE OPENING THE DISC PACKAGE, CAREFULLY
READ THE TERMS AND CONDITIONS OF THE LICENSE
AGREEMENT & WARANTEE LIMITATION ON THE PREVIOUS
PAGES.
1129