ZeePedia

Everything is an Object:You manipulate objects with references, Your first Java program

<< Introduction to Objects:The progress of abstraction, An object has an interface
Controlling Program Flow:Using Java operators, Execution control, true and false >>
img
only produces critical information for the next analysis, design, and
implementation pass, it also creates a code foundation.
That said, if you're looking at a methodology that contains tremendous
detail and suggests many steps and documents, it's still difficult to know
when to stop. Keep in mind what you're trying to discover:
1.
What are the objects? (How do you partition your project into its
component parts?)
2.
What are their interfaces? (What messages do you need to send to
each object?)
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.
Phase 0: Make a plan
You must first decide what steps you're going to have in your process. It
sounds simple (in fact, all of this sounds simple), and yet people often
don't make this decision before they start coding. If your plan is "let's
jump in and start coding," fine. (Sometimes that's appropriate when you
have a well-understood problem.) At least agree that this is the plan.
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).
74
Thinking in Java
img
When I began to study story structure (so that I will someday write a
novel) I was initially resistant to the idea of structure, feeling that I wrote
best when I simply let it flow onto the page. But I later realized that when
I write about computers the structure is clear enough to me that I don't
have to think about it very much. But I still structure my work, albeit only
semi-consciously in my head. Even if you think that your plan is to just
start coding, you still somehow go through the subsequent phases while
asking and answering certain questions.
The mission statement
Any system you build, no matter how complicated, has a fundamental
purpose; the business that it's in, the basic need that it satisfies. If you can
look past the user interface, the hardware- or system-specific details, the
coding algorithms and the efficiency problems, you will eventually find
the core of its being--simple and straightforward. Like the so-called high
concept from a Hollywood movie, you can describe it in one or two
sentences. This pure description is the starting point.
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."
Phase 1: What are we making?
In the previous generation of program design (called procedural design),
this is called "creating the requirements analysis and system
specification." These, of course, were places to get lost; intimidatingly
named documents that could become big projects in their own right. Their
intention was good, however. The requirements analysis says "Make a list
of the guidelines we will use to know when the job is done and the
Chapter 1: Introduction to Objects
75
img
customer is satisfied." The system specification says "Here's a description
of what the program will do (not how) to satisfy the requirements." The
requirements analysis is really a contract between you and the customer
(even if the customer works within your company, or is some other object
or system). The system specification is a top-level exploration into the
problem and in some sense a discovery of whether it can be done and how
long it will take. Since both of these will require consensus among people
(and because they will usually change over time), I think it's best to keep
them as bare as possible--ideally, to lists and basic diagrams--to save
time. You might have other constraints that require you to expand them
into bigger documents, but by keeping the initial document small and
concise, it can be created in a few sessions of group brainstorming with a
leader who dynamically creates the description. This not only solicits
input from everyone, it also fosters initial buy-in and agreement by
everyone on the team. Perhaps most importantly, it can kick off a project
with a lot of enthusiasm.
It's necessary to stay focused on the heart of what you're trying to
accomplish in this phase: determine what the system is supposed to do.
The most valuable tool for this is a collection of what are called "use
cases." Use cases identify key features in the system that will reveal some
of the fundamental classes you'll be using. These are essentially
descriptive answers to questions like9:
·
"Who will use this system?"
·
"What can those actors do with the system?"
·
"How does this actor do that with this system?"
·
"How else might this work if someone else were doing this, or if
the same actor had a different objective?" (to reveal variations)
·
"What problems might happen while doing this with the system?"
(to reveal exceptions)
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"
9 Thanks for help from James H Jarrett.
76
Thinking in Java
img
is referred to as a scenario, and a use case can be considered a collection
of scenarios. You can think of a scenario as a question that starts with:
"What does the system do if...?" For example, "What does the auto-teller
do if a customer has just deposited a check within the last 24 hours, and
there's not enough in the account without the check having cleared to
provide a desired withdrawal?"
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
Each stick person represents an "actor," which is typically a human or
some other kind of free agent. (These can even be other computer
systems, as is the case with "ATM.") The box represents the boundary of
your system. The ellipses represent the use cases, which are descriptions
of valuable work that can be performed with the system. The lines
between the actors and the use cases represent the interactions.
It doesn't matter how the system is actually implemented, as long as it
looks like this to the user.
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:
Chapter 1: Introduction to Objects
77
img
Greenhouse
Maintain
Growing
Temperature
Gardener
The use cases produce the requirements specifications by determining all
the interactions that the user may have with the system. You try to
discover a full set of use cases for your system, and once you've done that
you have the core of what the system is supposed to do. The nice thing
about focusing on use cases is that they always bring you back to the
essentials and keep you from drifting off into issues that aren't critical for
getting the job done. That is, if you have a full set of use cases, you can
describe your system and move onto the next phase. You probably won't
get it all figured out perfectly on the first try, but that's OK. Everything
will reveal itself in time, and if you demand a perfect system specification
at this point you'll get stuck.
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).
78
Thinking in Java
img
Larry Constantine and Lucy Lockwood, (Addison-Wesley Longman, 1999)
or go to www.ForUse.com.
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.
Phase 2: How will we build it?
In this phase you must come up with a design that describes what the
classes look like and how they will interact. An excellent technique in
determining classes and interactions is the Class-Responsibility-
Collaboration (CRC) card. Part of the value of this tool is that it's so low-
tech: you start out with a set of blank 3 x 5 cards, and you write on them.
Each card represents a single class, and on the card you write:
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.
Chapter 1: Introduction to Objects
79
img
2.
The "responsibilities" of the class: what it should do. This can
typically be summarized by just stating the names of the member
functions (since those names should be descriptive in a good
design), but it does not preclude other notes. If you need to seed
the process, look at the problem from a lazy programmer's
standpoint: What objects would you like to magically appear to
solve your problem?
3.
The "collaborations" of the class: what other classes does it interact
with? "Interact" is an intentionally broad term; it could mean
aggregation or simply that some other object exists that will
perform services for an object of the class. Collaborations should
also consider the audience for this class. For example, if you create
a class Firecracker, who is going to observe it, a Chemist or a
Spectator? The former will want to know what chemicals go into
the construction, and the latter will respond to the colors and
shapes released when it explodes.
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.
80
Thinking in Java
img
Before I began using CRC cards, the most successful consulting
experiences I had when coming up with an initial design involved
standing in front of a team--who hadn't built an OOP project before--and
drawing objects on a whiteboard. We talked about how the objects should
communicate with each other, and erased some of them and replaced
them with other objects. Effectively, I was managing all the "CRC cards"
on the whiteboard. The team (who knew what the project was supposed to
do) actually created the design; they "owned" the design rather than
having it given to them. All I was doing was guiding the process by asking
the right questions, trying out the assumptions, and taking the feedback
from the team to modify those assumptions. The true beauty of the
process was that the team learned how to do object-oriented design not by
reviewing abstract examples, but by working on the one design that was
most interesting to them at that moment: theirs.
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.
UML also provides an additional diagramming notation for describing the
dynamic model of your system. This is helpful in situations in which the
state transitions of a system or subsystem are dominant enough that they
need their own diagrams (such as in a control system). You may also need
to describe the data structures, for systems or subsystems in which data is
a dominant factor (such as a database).
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
12 For starters, I recommend the aforementioned UML Distilled, 2nd edition.
13 Python (www.Python.org) is often used as "executable pseudocode."
Chapter 1: Introduction to Objects
81
img
provides enough structure so that it's not so bad if you discover them
later. In fact, the design of an object tends to happen in five stages,
throughout the process of program development.
Five stages of object design
The design life of an object is not limited to the time when you're writing
the program. Instead, the design of an object appears over a sequence of
stages. It's helpful to have this perspective because you stop expecting
perfection right away; instead, you realize that the understanding of what
an object does and what it should look like happens over time. This view
also applies to the design of various types of programs; the pattern for a
particular type of program emerges through struggling again and again
with that problem (This is chronicled in the book Thinking in Patterns
with Java, downloadable at ). Objects, too, have
their patterns that emerge through understanding, use, and reuse.
1. Object discovery. This stage occurs during the initial analysis of a
program. Objects may be discovered by looking for external factors and
boundaries, duplication of elements in the system, and the smallest
conceptual units. Some objects are obvious if you already have a set of
class libraries. Commonality between classes suggesting base classes and
inheritance may appear right away, or later in the design process.
2. Object assembly. As you're building an object you'll discover the
need for new members that didn't appear during discovery. The internal
needs of the object may require other classes to support it.
3. System construction. Once again, more requirements for an
object may appear at this later stage. As you learn, you evolve your
objects. The need for communication and interconnection with other
objects in the system may change the needs of your classes or require new
classes. For example, you may discover the need for facilitator or helper
classes, such as a linked list, that contain little or no state information and
simply help other classes function.
4. System extension. As you add new features to a system you may
discover that your previous design doesn't support easy system extension.
With this new information, you can restructure parts of the system,
possibly adding new classes or class hierarchies.
82
Thinking in Java
img
5. Object reuse. This is the real stress test for a class. If someone tries
to reuse it in an entirely new situation, they'll probably discover some
shortcomings. As you change a class to adapt to more new programs, the
general principles of the class will become clearer, until you have a truly
reusable type. However, don't expect most objects from a system design to
be reusable--it is perfectly acceptable for the bulk of your objects to be
system-specific. Reusable types tend to be less common, and they must
solve more general problems in order to be reusable.
Guidelines for object development
These stages suggest some guidelines when thinking about developing
your classes:
1.
Let a specific problem generate a class, then let the class grow and
mature during the solution of other problems.
2.
Remember, discovering the classes you need (and their interfaces)
is the majority of the system design. If you already had those
classes, this would be an easy project.
3.
Don't force yourself to know everything at the beginning; learn as
you go. This will happen anyway.
4.
Start programming; get something working so you can prove or
disprove your design. Don't fear that you'll end up with procedural-
style spaghetti code--classes partition the problem and help control
anarchy and entropy. Bad classes do not break good classes.
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.
Phase 3: Build the core
This is the initial conversion from the rough design into a compiling and
executing body of code that can be tested, and especially that will prove or
Chapter 1: Introduction to Objects
83
img
disprove your architecture. This is not a one-pass process, but rather the
beginning of a series of steps that will iteratively build the system, as
you'll see in Phase 4.
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.
Phase 4: Iterate the use cases
Once the core framework is running, each feature set you add is a small
project in itself. You add a feature set during an iteration, a reasonably
short period of development.
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.
84
Thinking in Java
img
You stop iterating when you achieve target functionality or an external
deadline arrives and the customer can be satisfied with the current
version. (Remember, software is a subscription business.) Because the
process is iterative, you have many opportunities to ship a product rather
than a single endpoint; open-source projects work exclusively in an
iterative, high-feedback environment, which is precisely what makes them
successful.
An iterative development process is valuable for many reasons. You can
reveal and resolve critical risks early, the customers have ample
opportunity to change their minds, programmer satisfaction is higher,
and the project can be steered with more precision. But an additional
important benefit is the feedback to the stakeholders, who can see by the
current state of the product exactly where everything lies. This may
reduce or eliminate the need for mind-numbing status meetings and
increase the confidence and support from the stakeholders.
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
14 At least one aspect of evolution is covered in Martin Fowler's book Refactoring:
improving the design of existing code (Addison-Wesley 1999), which uses Java examples
exclusively.
Chapter 1: Introduction to Objects
85
img
where your program goes from good to great, and where those issues that
you didn't really understand in the first pass become clear. It's also where
your classes can evolve from single-project usage to reusable resources.
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
.)
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.
86
Thinking in Java
img
Evolution also occurs when you build a system, see that it matches your
requirements, and then discover it wasn't actually what you wanted.
When you see the system in operation, you find that you really wanted to
solve a different problem. If you think this kind of evolution is going to
happen, then you owe it to yourself to build your first version as quickly as
possible so you can find out if it is indeed what you want.
Perhaps the most important thing to remember is that by default--by
definition, really--if you modify a class, its super- and subclasses will still
function. You need not fear modification (especially if you have a built-in
set of unit tests to verify the correctness of your modifications).
Modification won't necessarily break the program, and any change in the
outcome will be limited to subclasses and/or specific collaborators of the
class you change.
Plans pay off
Of course you wouldn't build a house without a lot of carefully drawn
plans. If you build a deck or a dog house your plans won't be so elaborate,
but you'll probably still start with some kind of sketches to guide you on
your way. Software development has gone to extremes. For a long time,
people didn't have much structure in their development, but then big
projects began failing. In reaction, we ended up with methodologies that
had an intimidating amount of structure and detail, primarily intended
for those big projects. These methodologies were too scary to use--it
looked like you'd spend all your time writing documents and no time
programming. (This was often the case.) I hope that what I've shown you
here suggests a middle path--a sliding scale. Use an approach that fits
your needs (and your personality). No matter how minimal you choose to
make it, some kind of plan will make a big improvement in your project as
opposed to no plan at all. Remember that, by most estimates, over 50
percent of projects fail (some estimates go up to 70 percent!).
By following a plan--preferably one that is simple and brief--and coming
up with design structure before coding, you'll discover that things fall
together far more easily than if you dive in and start hacking. You'll also
realize a great deal of satisfaction. It's my experience that coming up with
an elegant solution is deeply satisfying at an entirely different level; it
feels closer to art than technology. And elegance always pays off; it's not a
Chapter 1: Introduction to Objects
87
img
frivolous pursuit. 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.
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.
XP is both a philosophy about programming work and a set of guidelines
to do it. Some of these guidelines are reflected in other recent
methodologies, but the two most important and distinct contributions, in
my opinion, are "write tests first" and "pair programming." Although he
argues strongly for the whole process, Beck points out that if you adopt
only these two practices you'll greatly improve your productivity and
reliability.
Write tests first
Testing has traditionally been relegated to the last part of a project, after
you've "gotten everything working, but just to be sure." It's implicitly had
a low priority, and people who specialize in it have not been given a lot of
status and have often even been cordoned off in a basement, away from
the "real programmers." Test teams have responded in kind, going so far
as to wear black clothing and cackling with glee whenever they break
something (to be honest, I've had this feeling myself when breaking
compilers).
XP completely revolutionizes the concept of testing by giving it equal (or
even greater) priority than the code. In fact, you write the tests before you
write the code that will be tested, and the tests stay with the code forever.
The tests must be executed successfully every time you do an integration
of the project (which is often, sometimes more than once a day).
Writing tests first has two extremely important effects.
88
Thinking in Java
img
First, it forces a clear definition of the interface of a class. I've often
suggested that people "imagine the perfect class to solve a particular
problem" as a tool when trying to design the system. The XP testing
strategy goes further than that--it specifies exactly what the class must
look like, to the consumer of that class, and exactly how the class must
behave. In no uncertain terms. You can write all the prose, or create all
the diagrams you want, describing how a class should behave and what it
looks like, but nothing is as real as a set of tests. The former is a wish list,
but the tests are a contract that is enforced by the compiler and the
running program. It's hard to imagine a more concrete description of a
class than the tests.
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
Chapter 1: Introduction to Objects
89
img
right from the beginning? That's why you write them first, and run them
automatically with every build of your system. Your tests become an
extension of the safety net provided by the language.
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.
17 Including (especially) the PA system. I once worked in a company that insisted on
broadcasting every phone call that arrived for every executive, and it constantly
interrupted our productivity (but the managers couldn't begin to conceive of stifling such
an important service as the PA). Finally, when no one was looking I started snipping
speaker wires.
90
Thinking in Java
img
The value of pair programming is that one person is actually doing the
coding while the other is thinking about it. The thinker keeps the big
picture in mind--not only the picture of the problem at hand, but the
guidelines of XP. If two people are working, it's less likely that one of
them will get away with saying, "I don't want to write the tests first," for
example. And if the coder gets stuck, they can swap places. If both of them
get stuck, their musings may be overheard by someone else in the work
area who can contribute. Working in pairs keeps things flowing and on
track. Probably more important, it makes programming a lot more social
and fun.
I've begun using pair programming during the exercise periods in some of
my seminars and it seems to significantly improve everyone's experience.
Why Java succeeds
The reason Java has been so successful is that the goal was to solve many
of the problems facing developers today. The goal of Java is improved
productivity. This productivity comes in many ways, but the language is
designed to aid you as much as possible, while hindering you as little as
possible with arbitrary rules or any requirement that you use a particular
set of features. Java is designed to be practical; Java language design
decisions were based on providing the maximum benefits to the
programmer.
Systems are easier
to express and understand
Classes designed to fit the problem tend to express it better. This means
that when you write the code, you're describing your solution in the terms
of the problem space ("Put the grommet in the bin") rather than the terms
of the computer, which is the solution space ("Set the bit in the chip that
means that the relay will close"). You deal with higher-level concepts and
can do much more with a single line of code.
The other benefit of this ease of expression is maintenance, which (if
reports can be believed) takes a huge portion of the cost over a program's
lifetime. If a program is easier to understand, then it's easier to maintain.
Chapter 1: Introduction to Objects
91
img
This can also reduce the cost of creating and maintaining the
documentation.
Maximal leverage with libraries
The fastest way to create a program is to use code that's already written: a
library. A major goal in Java is to make library use easier. This is
accomplished by casting libraries into new data types (classes), so that
bringing in a library means adding new types to the language. Because the
Java compiler takes care of how the library is used--guaranteeing proper
initialization and cleanup, and ensuring that functions are called
properly--you can focus on what you want the library to do, not how you
have to do it.
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.
Programming in the large
Many traditional languages have built-in limitations to program size and
complexity. BASIC, for example, can be great for pulling together quick
solutions for certain classes of problems, but if the program gets more
than a few pages long, or ventures out of the normal problem domain of
that language, it's like trying to swim through an ever-more viscous fluid.
There's no clear line that tells you when your language is failing you, and
even if there were, you'd ignore it. You don't say, "My BASIC program just
got too big; I'll have to rewrite it in C!" Instead, you try to shoehorn a few
more lines in to add that one new feature. So the extra costs come
creeping up on you.
Java is designed to aid programming in the large--that is, to erase those
creeping-complexity boundaries between a small program and a large
one. You certainly don't need to use OOP when you're writing a "hello
world" style utility program, but the features are there when you need
92
Thinking in Java
img
them. And the compiler is aggressive about ferreting out bug-producing
errors for small and large programs alike.
Strategies for transition
If you buy into OOP, your next question is probably, "How can I get my
manager/colleagues/department/peers to start using objects?" Think
about how you--one independent programmer--would go about learning
to use a new language and a new programming paradigm. You've done it
before. First comes education and examples; then comes a trial project to
give you a feel for the basics without doing anything too confusing. Then
comes a "real world" project that actually does something useful.
Throughout your first projects you continue your education by reading,
asking questions of experts, and trading hints with friends. This is the
approach many experienced programmers suggest for the switch to Java.
Switching an entire company will of course introduce certain group
dynamics, but it will help at each step to remember how one person would
do it.
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.
An alternative approach that is sometimes suggested is the education of
all company levels at once, including overview courses for strategic
managers as well as design and programming courses for project builders.
This is especially good for smaller companies making fundamental shifts
in the way they do things, or at the division level of larger companies.
Because the cost is higher, however, some may choose to start with
Chapter 1: Introduction to Objects
93
img
project-level training, do a pilot project (possibly with an outside mentor),
and let the project team become the teachers for the rest of the company.
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.
3. Model from success
Seek out examples of good object-oriented design before starting from
scratch. There's a good probability that someone has solved your problem
already, and if they haven't solved it exactly you can probably apply what
you've learned about abstraction to modify an existing design to fit your
needs. This is the general concept of design patterns, covered in Thinking
in Patterns with Java, downloadable at .
4. Use existing class libraries
The primary economic motivation for switching to OOP is the easy use of
existing code in the form of class libraries (in particular, the Standard
Java libraries, which are covered throughout this book). The shortest
application development cycle will result when you can create and use
objects from off-the-shelf libraries. However, some new programmers
don't understand this, are unaware of existing class libraries, or, through
fascination with the language, desire to write classes that may already
exist. Your success with OOP and Java will be optimized if you make an
effort to seek out and reuse other people's code early in the transition
process.
5. Don't rewrite existing code in Java
It is not usually the best use of your time to take existing, functional code
and rewrite it in Java. (If you must turn it into objects, you can interface
to the C or C++ code using the Java Native Interface, described in
94
Thinking in Java
img
Appendix B.) There are incremental benefits, especially if the code is
slated for reuse. But chances are you aren't going to see the dramatic
increases in productivity that you hope for in your first few projects unless
that project is a new one. Java and OOP shine best when taking a project
from concept to reality.
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.
Chapter 1: Introduction to Objects
95
img
Performance issues
A common question is, "Doesn't OOP automatically make my programs a
lot bigger and slower?" The answer is, "It depends." The extra safety
features in Java have traditionally extracted a performance penalty over a
language like C++. Technologies such as "hotspot" and compilation
technologies have improved the speed significantly in most cases, and
efforts continue toward higher performance.
When your focus is on rapid prototyping, you can throw together
components as fast as possible while ignoring efficiency issues. If you're
using any third-party libraries, these are usually already optimized by
their vendors; in any case it's not an issue while you're in rapid-
development mode. When you have a system that you like, if it's small and
fast enough, then you're done. If not, you begin tuning with a profiling
tool, looking first for speedups that can be done by rewriting small
portions of code. If that doesn't help, you look for modifications that can
be made in the underlying implementation so no code that uses a
particular class needs to be changed. Only if nothing else solves the
problem do you need to change the design. The fact that performance is so
critical in that portion of the design is an indicator that it must be part of
the primary design criteria. You have the benefit of finding this out early
using rapid development.
If you find a function that is a particular bottleneck, you can rewrite it in
C/C++ using Java's native methods, the subject of Appendix B.
Common design errors
When starting your team into OOP and Java, programmers will typically
go through a series of common design errors. This often happens due to
insufficient feedback from experts during the design and implementation
of early projects, because no experts have been developed within the
company, and because there may be resistance to retaining consultants.
It's easy to feel that you understand OOP too early in the cycle and go off
on a bad tangent. Something that's obvious to someone experienced with
the language may be a subject of great internal debate for a novice. Much
of this trauma can be skipped by using an experienced outside expert for
training and mentoring.
96
Thinking in Java
img
Java vs. C++?
Java looks a lot like C++, and so naturally it would seem that C++ will be
replaced by Java. But I'm starting to question this logic. For one thing,
C++ still has some features that Java doesn't, and although there have
been a lot of promises about Java someday being as fast or faster than
C++, we've seen steady improvements but no dramatic breakthroughs.
Also, there seems to be a continuing interest in C++, so I don't think that
language is going away any time soon. (Languages seem to hang around.
Speaking at one of my "Intermediate/Advanced Java Seminars," Allen
Holub asserted that the two most commonly used languages are Rexx and
COBOL, in that order.)
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
Chapter 1: Introduction to Objects
97
img
there), or if you have an existing C or C++ code base, Java might slow
your development down rather than speeding it up.
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 ).
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
98
Thinking in Java
img
the kinds of issues you will encounter when moving your own company to
OOP and Java.
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.
18 In particular, I recommend looking at Python (http://www.Python.org).
Chapter 1: Introduction to Objects
99
img
2: Everything
is an Object
Although it is based on C++, Java is more of a "pure"
object-oriented language.
Both C++ and Java are hybrid languages, but in Java the designers felt
that the hybridization was not as important as it was in C++. A hybrid
language allows multiple programming styles; the reason C++ is hybrid is
to support backward compatibility with the C language. Because C++ is a
superset of the C language, it includes many of that language's
undesirable features, which can make some aspects of C++ overly
complicated.
The Java language assumes that you want to do only object-oriented
programming. This means that before you can begin you must shift your
mindset into an object-oriented world (unless it's already there). The
benefit of this initial effort is the ability to program in a language that is
simpler to learn and to use than many other OOP languages. In this
chapter we'll see the basic components of a Java program and we'll learn
that everything in Java is an object, even a Java program.
You manipulate objects
with references
Each programming language has its own means of manipulating data.
Sometimes the programmer must be constantly aware of what type of
manipulation is going on. Are you manipulating the object directly, or are
you dealing with some kind of indirect representation (a pointer in C or
C++) that must be treated with a special syntax?
101
img
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.)
102
Thinking in Java
img
However, this uses a special Java feature: strings can be initialized with
quoted text. Normally, you must use a more general type of initialization
for objects.
You must create
all the objects
When you create a reference, you want to connect it with a new object.
You do so, in general, with the new keyword. new says, "Make me a new
one of these objects." So in the above example, you can say:
String s = new String("asdf");
Not only does this mean "Make me a new String," but it also gives
information about how to make the String by supplying an initial
character string.
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.
Where storage lives
It's useful to visualize some aspects of how things are laid out while the
program is running, in particular how memory is arranged. There are six
different places to store data:
1.
Registers. This is the fastest storage because it exists in a place
different from that of other storage: inside the processor. However,
the number of registers is severely limited, so registers are
allocated by the compiler according to its needs. You don't have
direct control, nor do you see any evidence in your programs that
registers even exist.
2.
The stack. This lives in the general RAM (random-access
memory) area, but has direct support from the processor via its
stack pointer. The stack pointer is moved down to create new
Chapter 2: Everything is an Object
103
img
memory and moved up to release that memory. This is an
extremely fast and efficient way to allocate storage, second only to
registers. The Java compiler must know, while it is creating the
program, the exact size and lifetime of all the data that is stored on
the stack, because it must generate the code to move the stack
pointer up and down. This constraint places limits on the flexibility
of your programs, so while some Java storage exists on the stack--
in particular, object references--Java objects themselves are not
placed on the stack.
3.
The heap. This is a general-purpose pool of memory (also in the
RAM area) where all Java objects live. The nice thing about the
heap is that, unlike the stack, the compiler doesn't need to know
how much storage it needs to allocate from the heap or how long
that storage must stay on the heap. Thus, there's a great deal of
flexibility in using storage on the heap. Whenever you need to
create an object, you simply write the code to create it using new,
and the storage is allocated on the heap when that code is executed.
Of course there's a price you pay for this flexibility: it takes more
time to allocate heap storage than it does to allocate stack storage
(that is, if you even could create objects on the stack in Java, as you
can in C++).
4.
Static storage. "Static" is used here in the sense of "in a fixed
location" (although it's also in RAM). Static storage contains data
that is available for the entire time a program is running. You can
use the static keyword to specify that a particular element of an
object is static, but Java objects themselves are never placed in
static storage.
5.
Constant storage. Constant values are often placed directly in
the program code, which is safe since they can never change.
Sometimes constants are cordoned off by themselves so that they
can be optionally placed in read-only memory (ROM).
6.
Non-RAM storage. If data lives completely outside a program it
can exist while the program is not running, outside the control of
the program. The two primary examples of this are streamed
objects, in which objects are turned into streams of bytes, generally
104
Thinking in Java
img
to be sent to another machine, and persistent objects, in which the
objects are placed on disk so they will hold their state even when
the program is terminated. The trick with these types of storage is
turning the objects into something that can exist on the other
medium, and yet can be resurrected into a regular RAM-based
object when necessary. Java provides support for lightweight
persistence, and future versions of Java might provide more
complete solutions for persistence.
Special case: primitive types
There is a group of types that gets special treatment; you can think of
these as "primitive" types that you use quite often in your programming.
The reason for the special treatment is that to create an object with new--
especially a small, simple variable--isn't very efficient because new places
objects on the heap. For these types Java falls back on the approach taken
by C and C++. That is, instead of creating the variable using new, an
"automatic" variable is created that is not a reference. The variable holds
the value, and it's placed on the stack so it's much more efficient.
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.
Primitive
Size
Minimum
Maximum
Wrapper
type
type
boolean
--
--
--
Boolean
char
16-bit
Unicode 0
Unicode 216- 1
Character
byte
8-bit
-128
+127
Byte
short
16-bit
-215
+215--1
Short
int
32-bit
-231
+231--1
Integer
long
64-bit
-263
+263--1
Long
float
32-bit
IEEE754
IEEE754
Float
double
64-bit
IEEE754
IEEE754
Double
void
--
--
--
Void
All numeric types are signed, so don't go looking for unsigned types.
Chapter 2: Everything is an Object
105
Table of Contents:
  1. Introduction to Objects:The progress of abstraction, An object has an interface
  2. Everything is an Object:You manipulate objects with references, Your first Java program
  3. Controlling Program Flow:Using Java operators, Execution control, true and false
  4. Initialization & Cleanup:Method overloading, Member initialization
  5. Hiding the Implementation:the library unit, Java access specifiers, Interface and implementation
  6. Reusing Classes:Composition syntax, Combining composition and inheritance
  7. Polymorphism:Upcasting revisited, The twist, Designing with inheritance
  8. Interfaces & Inner Classes:Extending an interface with inheritance, Inner class identifiers
  9. Holding Your Objects:Container disadvantage, List functionality, Map functionality
  10. Error Handling with Exceptions:Basic exceptions, Catching an exception
  11. The Java I/O System:The File class, Compression, Object serialization, Tokenizing input
  12. Run-time Type Identification:The need for RTTI, A class method extractor
  13. Creating Windows & Applets:Applet restrictions, Running applets from the command line
  14. Multiple Threads:Responsive user interfaces, Sharing limited resources, Runnable revisited
  15. Distributed Computing:Network programming, Servlets, CORBA, Enterprise JavaBeans
  16. A: Passing & Returning Objects:Aliasing, Making local copies, Cloning objects
  17. B: The Java Native Interface (JNI):Calling a native method, the JNIEnv argument
  18. Java Programming Guidelines:Design, Implementation
  19. Resources:Software, Books, My own list of books
  20. Index