Agile Java Crafting Code With Test Driven Development TM Crafting Code With Test Driven Development 2004114916 0131482394 9780131482395 Compress
Agile Java Crafting Code With Test Driven Development TM Crafting Code With Test Driven Development 2004114916 0131482394 9780131482395 Compress
“At long last a book that integrates solid beginning lessons in both Java and
Test-Driven Development! I know most of the software development appren-
tices that go through my facility could have avoided the pain of unlearning
years of ad hoc programming habits if Jeff’s book had been available when
they first learned Java.”
Steven A. Gordon, Ph.D., Manager,
Arizona State University Software Factory (http://sf.asu.edu/)
“Jeff draws on his exceptionally deep expertise in Java and Agile processes to
show us a remarkably powerful approach for building Java programs that are
clean, well-structured, and thoroughly tested. Highly recommended! ”
Paul Hodgetts, Founder and CEO, Agile Logic, Inc.
“A great way to learn Java. Agile Java takes you through not only the basics
of the Java language, setup, and tools, but also provides excellent instruction
on concepts of test-driven development, refactoring, and Object-Oriented
programming. This book is required reading for our engineering team. ”
Andrew Masters, President and CIO, 5two8, Inc.
“Agile Java is a must-read for anyone developing Java applications. The text
contains innovative and insightful ideas on how to write high-quality, extensi-
ble Java applications faster and more efficiently than traditional methods.
Beginner and experienced developers will learn a tremendous amount from
the concepts taught in this book. ”
Bret McInnis, Vice President eBusiness Technologies, Corporate Express
This page intentionally left blank
Agile Java
Robert C. Martin Series
The mission of this series is to improve the state of the art of software craftsmanship.
The books in this series are technical, pragmatic, and substantial. The authors are
highly experienced craftsmen and professionals dedicated to writing about what
actually works in practice, as opposed to what might work in theory. You will read
about what the author has done, not what he thinks you should do. If the book is
about programming, there will be lots of code. If the book is about managing, there
will be lots of case studies from real projects.
These are the books that all serious practitioners will have on their bookshelves.
These are the books that will be remembered for making a difference and for guiding
professionals to become true craftsman.
Jeff Langr
Text printed in the United States on recycled paper at Phoenix Book Tech.
Third printing, October 2007
In memory of Edmund Langr and Heather Mooney
This page intentionally left blank
Contents
Foreword . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .xxi
Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .xxiii
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1
Who Is This Book For? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .3
What This Book Is Not . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .4
How to Use This Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .5
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .6
Conventions Used in This Book . . . . . . . . . . . . . . . . . . . . . . . . . . .6
An Agile Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .9
What Is “Agile?” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .9
What Is Java? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .11
Why OO? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .13
What Is an Object? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .13
What Are Classes? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .14
Why UML? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .16
What Is Inheritance? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .17
Why Test-Driven Development? . . . . . . . . . . . . . . . . . . . . . . . . . .18
Setting Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .21
Software You’ll Need . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .21
Does It Work? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .26
Compiling Hello World . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .27
Executing Hello World . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .27
Still Stuck? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .28
ix
x CONTENTS
Refactoring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .92
Creating Dates with Calendar . . . . . . . . . . . . . . . . . . . . . . . . . . . .95
Comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .96
Javadoc Comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .97
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .100
Short-Circuiting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .313
Hash Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .313
Courses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .315
Refactoring Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .317
Equality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .323
The Contract for Equality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .326
Apples and Oranges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .327
Collections and Equality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .329
Hash Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .330
Collisions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .332
An Ideal Hash Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .333
A Final Note on hashCode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .335
More on Using HashMaps . . . . . . . . . . . . . . . . . . . . . . . . . . . . .337
Additional Hash Tables and Set Implementations . . . . . . . . . . . .341
toString . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .343
Strings and Equality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .345
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .346
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .735
About the Author
Jeff Langr is an independent software consultant with a score and more years
of development experience. He provides expertise to customers in software
development, design, and agile processes through his company Langr
Software Solutions (http://www.LangrSoft.com).
Langr worked for Uncle Bob Martin for two years at the esteemed Object
Mentor, recognized as the premier XP consulting firm. He consulted at and
has been employed by several Fortune 500 companies, as well as the obliga-
tory failed dot-com.
Langr’s background includes teaching a university course on Java. He has
successfully taught hundreds of other professional students in Java, TDD, XP,
and object-oriented development. Langr has spoken about software develop-
ment numerous times at national conferences and local user group meetings.
Langr wrote the Prentice Hall book Essential Java Style (Langr2000), a
guide for building quality Java code. Five years later you can still find blog
entries of people who swear by their dog-eared copy. Several of Langr’s arti-
cles on Java and TDD have appeared in Software Development, C/C++ Users
Journal, and various online magazine sites including Developer.com. You can
find links to these and other articles at http://www.langrsoft.com/resources
.html.
He would like to add that he lives for software development, and hopes his
love for the craft of building code comes through in Agile Java. Langr resides
in Colorado Springs with his wife Kathy and his children Katie, Tim, and
Anna.
xix
This page intentionally left blank
Foreword
Jeff Langr has written a very interesting Java book in Agile Java: Crafting
Code with Test-Driven Development. Its purpose is to teach new program-
mers the Java language, and to do so in the context of the best development
approach that he and I know—namely Test-Driven Development (TDD). It’s
an undertaking of great potential value, and Jeff has done it nicely. It’s my
pleasure to provide this foreword and to recommend this book.
Agile Java isn’t just for rank beginners. It’s also a good book for bringing
experienced programmers new to the Java language up to speed. It won’t
replace a certification manual or one of those huge books of “Everything
Java.” That’s not its point. The point of Agile Java is to get you up to speed.
Better yet, you come up to speed using TDD, which will serve you well in
future learning and in your day-to-day work.
The book starts right off with object-oriented concepts and ideas. It helps if
you know a bit about objects when you step in, but if you don’t, hang on and
you should pick up the basic ideas as you go along. Furthermore, every step of
the way you’ll be using the Test-Driven Development technique. If you haven’t
tried TDD, it may seem a bit odd at the beginning, but if you’re like most of us
who have given it a fair try, it will become a frequently-used tool in your kit.
If you already have Java and JUnit set up on your machine, dig right in. If
not, be sure to use the “Setting Up” chapter to get your system correctly con-
figured before moving on to the real examples. Once you can compile and
run a simple Java program on your machine, you’re ready to go.
Jeff asks you to type in the tests and example code, and I would echo that
request. The TDD discipline is one that is learned by doing and practicing,
not just by reading. You need to develop your own sense of the rhythm of
development. Besides, typing in the examples from any programming book is
the best way to learn what it has to offer.
In Agile Java, Jeff helps you build pieces of two applications. One of these
relates to a student information system, the other focuses on playing chess. By
the time you have worked through all the chapters, Jeff has introduced you to
the basics of Java. Perhaps more importantly, you have met some of the most
important deep capabilities, including interfaces, polymorphism, mock
objects, reflection, multi-threading, and generics.
I found that Lesson 10, regarding the mathematical features of Java, particu-
larly brought out the way I like to use tests as part of my learning of a new fea-
xxi
xxii FOREWORD
ture of a language or library. It’s easy to read about something like BigDecimal
and think, “I get it.” For a while, I might even really get it. But when I encode
my learning as tests, two things happen: First, I learn things about the topic
that I would have missed if just reading about it. Writing the code pounds ideas
into my thick head a bit better than just reading. Second, the tests record what
I’ve learned as well as my thought process as I learned it. Because I’ve devel-
oped the habit of saving all my test cases, I can refer back to them and quickly
refresh my memory. Often I’ll even put a book and page reference into the tests
as a comment, in case I want to go back later and dig out more.
Lesson 11, regarding I/O, includes a nice example of something I’m not
very familiar with. Since I don’t work in Java much, and most of the lan-
guages I commonly use don’t have an equivalent, I’m not familiar with nested
classes. Jeff gives a good example of when we would be well-advised to use
nested classes, and shows how to use and test them.
As I write this foreword, I’m really getting into the book, because Jeff is tak-
ing me where I’ve not gone before. I like that. Lesson 12 is about Mock Objects,
and the first example is one that we agile software developers encounter often:
How can we deal in our incremental development with an external API that is
fairly well defined but isn’t available yet? Jeff shows us how to do this by defin-
ing the interface—from the documentation if necessary—and then by building a
Mock Object that represents our understanding of what the API will do when
we finally get it. Writing tests against our Mock Object gives us tests that we can
use to ensure that the API does what we expect when we finally get the real
code. An excellent addition to your bag of tricks!
Jeff is an educator, and a darn good one. Jeff wants us to think, and to
work! He knows that if you and I are ready to learn, we have to practice: we
have to do the work. His chapters have exercises. We are well-advised to
think about all of them, and to do the ones that cover topics we aren’t famil-
iar with. That’s how we’ll really hammer these ideas into our heads. Read and
study his examples, type them in to drill them into your mind, and then fol-
low his lead as you work the interesting examples. You’ll be glad you did.
Agile Java: Crafting Code with Test-Driven Development offers you at least
three benefits: You’ll learn things about Java that you probably didn’t know,
even if you’re not an absolute beginner. You’ll learn how to use test-driven
development in a wide range of cases, including some in which you’d probably
find difficult to invent on your own. And, through building up your skill,
you’ll add this valuable technique to your professional bag of tricks.
I enjoyed the book and found it valuable. I think you will, too. Enjoy!
Ron Jeffries
www.XProgramming.com
Pinckney, Michigan
November 2, 2004
Acknowledgments
xxiii
This page intentionally left blank
Introduction
Introduction
1
See [McBreen2000].
1
2 INTRODUCTION
Introduction In contrast, the high volume of rapid feedback in TDD constantly rein-
forces correct coding and quickly points out incorrect coding. The classic
code-run-and-observe approach provides feedback, but at a much slower
rate. Unfortunately, it is currently the predominant method of teaching pro-
gramming.
Others have attempted more innovative approaches to teaching. In the
1990s, Adele Goldberg created a product known as LearningWorks designed
for teaching younger students. It allowed a user to directly manipulate visual
objects by dynamically executing bits of code. The user saw immediate re-
sults from their actions. A recent Java training tool uses a similar approach.
It allows the student to execute bits of code to produce visual effects on
“live” objects.
The problem with approaches like these is that they are bound to the
learning environment. Once you complete the training, you must still learn
how to construct your own system from the ground up, without the use of
these constrained tools. By using TDD as the driver for learning, you are
taught an unbounded technique that you can continue to use in your profes-
sional software development career.
Agile Java takes the most object-oriented approach feasible. Part of the
difficulty in learning Java is the “bootstrapping” involved. What is the mini-
mum you must learn in order to be able to write classes of some substance?
Most books start by teaching you the prototypical first Java program—the
“hello world” application. But it is quite a mouthful: class Hello { public static
void main(String[] args) { System.out.println("hello world"); } }. This brief program
contains at least a dozen concepts that you must ultimately learn. Worse, out
of those dozen concepts, at least three are nonobject-oriented concepts that
you are better off learning much later.
In this book, you will learn the right way to code from the start and come
back to fully understand “hello world” later in the book.2 Using TDD, you
will be able to write good object-oriented code immediately. You’ll still have
a big initial hurdle to get over, but this approach keeps you from having to
first understand not-very-object-oriented concepts such as static methods and
arrays. You will learn all core Java concepts in due time, but your initial em-
phasis is on objects.
Agile Java presents a cleaner break from the old way of doing things. It al-
lows you to pretend for a while that there was never a language called C, the
syntactical basis for Java that has been around for 30 years. While C is a
2
Don’t fret, though: You’ll actually start with the “hello world” application in order
to ensure you can compile and execute Java code. But you won’t have to understand
it all until later.
WHO IS THIS BOOK FOR? 3
great language, its imprint on Java left a quite a few constructs that can dis- Who Is This
tract you from building good object-oriented systems. Using Agile Java, you Book For?
can learn the right way of doing things before having to understand these
legacies of the Java language.
TDD Disclaimer
Some developers experienced in TDD will note stylistic differences between
their approach and my approach(es) in Agile Java. There are many ways to
do TDD. None of these techniques are perfect or ordained as the absolute
right way to do things. Do whatever works best for you. Do what makes the
most sense, as long as it doesn’t violate the basic tenets set forth in Agile
Java.
HOW TO USE THIS BOOK 5
Readers will also find areas in which the code could be improved.3 Even as How to Use This
a beginning developer, you no doubt will encounter code in Agile Java that Book
you don’t like. Let me know. Send in your suggestions, and I may incorporate
them in the next edition. And fix your own implementations. You can im-
prove almost any code or technique. Do!
After the first lesson in Agile Java, the tests appear wholesale, as if they
were coded in one fell swoop. This is not the case: Each test was built asser-
tion by assertion, in much smaller increments than the book can afford to
present. Keep this in mind when writing your own code—one of the most im-
portant aspects of TDD is taking small, small steps with constant feedback.
And when I say small, I mean small! If you think you’re taking small steps,
try taking even smaller steps.
3
A constant developer pair would have helped.
6 INTRODUCTION
Conventions tent is to give you some ideas for how to build them using TDD. The third
Used in This additional lesson presents an overview for a number of Java topics, things
Book that most Java developers will want to know.
For the most effective learning experience, you should follow along with
the lessons by entering and executing each bit of test and implementation
code as I present it. While the code is available for downloading (see the next
paragraph), I highly recommend that you type each bit of code yourself. Part
of doing TDD correctly is getting a good understanding of the rhythm in-
volved in going back and forth between the tests and the code. If you just
download the code and execute it, you’re not going to learn nearly as much.
The tactile response of the keyboard seems to impart a lot of learning.
However, who am I to make you work in a certain way? You may choose
to download the code from http://www.LangrSoft.com/agileJava/code. I’ve
organized this code by lesson. I have made available the code that is the re-
sult of working each lesson—in the state it exists by the end of the lesson.
This will make it easier for you to pick up at any point, particularly since
many of the examples carry through to subsequent lessons.
Exercises
Each of the fifteen core lessons in Agile Java has you build bits and pieces of
a student information system for a university. I chose this single common
theme to help demonstrate how you can incrementally build upon and ex-
tend existing code. Each lesson also finishes with a series of exercises. Instead
of the student information system, the bulk of the exercises, provided by Jeff
Bay, have you build bits and pieces of a chess application.
Some of the exercises are involved and quite challenging. But I highly rec-
ommend that you do every one. The exercises are where the real learning
starts—you’re figuring out how to solve problems using Java, without my
help. Doing all of the exercises will give you a second opportunity to let each
lesson sink in.
The dialog in Agile Java alternates between expressing ideas in English and
expressing them in code. For example, I may refer to the need to cancel a
payroll check or I might choose to say that you should send the cancel message
to the PayrollCheck object. The idea is to help you start making the neces-
sary connections between understanding requirements and expressing them
in code.
Class names are by convention singular nouns, such as Customer. “You
use the Customer class to create multiple Customer objects.” In the name of
readability, I will sometimes refer to these Customer objects using the plural
of the class name: “The collection contains all of the Customers loaded from
the file system.”
New terms initially appear in italics. Most of these terms appear in the
Glossary (Appendix A).
Throughout Agile Java, you will take specifications and translate them
into Java code. In the tradition of agile processes, I present these specifica-
tions using informal English. They are requirements, also known as stories. A
story is a promise for more conversation. Often you will need to continue a
dialog with the presenter of the story (sometimes known as the customer) in
order to obtain further details on a story. In the case of Agile Java, if a story
8 INTRODUCTION
Conventions isn’t making sense, try reading a bit further. Read the corresponding tests to
Used in This see how they interpret the story.
Book I have highlighted stories throughout Agile Java with a “story-
telling” icon. The icon emphasizes the oral informality of
stories.
Your best path to learning the craft of programming is to work with an
experienced practitioner. You will discover that there are many “secrets” to
programming. Some secrets are basic concepts that you must know in order
to master the craft. Other secrets represent pitfalls—things to watch out for.
You must bridge these challenges, and remember what you learned, to be a
successful Java programmer.
I’ve marked such critical points with a bridge icon (the bridge is
crossing over the pitfalls). Many of these critical points will apply to
development in any language, not just Java.
An Agile Overview
What Is “Agile?”
This chapter provides a brief introduction to some of the core concepts in the
book, including Java, object-oriented programming, and test-driven develop-
ment. You will get answers to the following questions:
• What is “agile”?
• What is Java?
• What is object-oriented programming?
• Why OO?
• What is an object?
• What are classes?
• Why UML?
• What is inheritance?
• Why test-driven development?
What Is “Agile?”
This book is called Agile Java. “Agile” is a new catchphrase for describing a
bunch of related software development methodologies. Loosely defined, a
methodology is a process for building software. There are many ways to ap-
proach constructing software as part of a team; many of these approaches have
been formalized into published methodologies. There are dozens of major
processes in use; you may have heard the names of some of them: waterfall,
RUP (Rational Unified Process), XP (extreme programming), and Scrum.
Methodologies have evolved as we learn more about how to effectively
work. Waterfall is an older approach to building software that promotes co-
pious documentation, rigid up-front (i.e., at the start of the project) defini-
tions of requirements and system design, and division of a project into
serialized phases. Its name derives from a diagram that shows progress flow-
ing down from one phase to the next.
9
10 AN AGILE OVERVIEW
While waterfall may be appropriate for some efforts, it has some limita-
tions that can cause severe problems on other software development projects.
Using a waterfall process means that you are less able to adapt to change as a
project progresses. For these reasons, waterfall is sometimes referred to as a
What Is “Agile?” heavyweight process. It is heavy because it requires considerable baggage and
restricts development teams from being able to rapidly respond to change.
In contrast, agile processes, a fairly recent phenomena, are lightweight
processes. When following an agile process, there is less emphasis on written
documentation and on having everything finalized up front. Agile processes
are designed around accommodating change. XP is probably the most well-
known example of an agile process.
RUP is another very well-known process. Technically RUP is a process
framework: It supplies a catalog of things you can mix and match in order to
create your own customized process. Such a RUP process instance can strad-
dle the fence between being a heavyweight and an agile process.
When following any kind of process, heavyweight or agile, you must al-
ways do the following things when building software:
• analysis: determine what you need to build by gathering and refining
requirements for what the software should do
• planning: figure out how long it will take to build the software
• design: determine how to put together what you are building
• coding: construct the software using one or more programming lan-
guages
• testing: ensure that what you built works
• deployment: deliver the software to the environment in which it will be
executed and used
• documentation: describe the software for various audiences, including
end users who need to know how to use the software and developers
who need to know how to maintain the software
• review: ensure, through peer consensus, that the software remains
maintainable and retains high quality
If you are not doing all of these things, you are hacking—producing code
with abandon and no consideration for business needs. Some processes such
as XP appear not to promote everything in the above list. In reality, they do,
but in a different, kind-of-sideways fashion.
I cover little with respect to methodology in this book. You can find
shelves of books devoted to learning about methodologies. Most develop-
WHAT IS JAVA? 11
What Is Java?
When you hear people refer to Java, they often mean the Java language. The
Java language allows you to write instructions, or code, that your computer will
interpret in order to execute a software application, or program. Examples of ap-
plications are Microsoft Word, Netscape, and the little clock that runs in the cor-
ner of your monitor. Coding, or programming, is the act of writing programs.
Another thing people mean when they refer to Java is the Java platform.
The term platform usually indicates an underlying operating system, such as
Windows or Unix™, on which you can run applications.1 Java acts as a plat-
form: It not only defines a language but also gives you a complete environ-
ment in which to build and execute your programs. The Java platform acts as
a layer between your application and the underlying operating system; it is a
mini–operating system in itself. This allows you to write code in a single lan-
guage and execute it on virtually all major operating systems today.
You will download the Java software development kit (SDK), which pro-
vides you with three main components:
• a compiler (javac)
• a virtual machine, or VM (java)
• a class library, or API (application programming interface)
1
[WhatIs2004].
12 AN AGILE OVERVIEW
The compiler is a program that reads Java source files, ensures they con-
tain valid Java code, and produces class files. A source file is a text file that
contains the code you type. The class files you generate using the compiler
contain byte codes that represent the code you typed in. Byte codes appear in
What Is Java? a format that the VM can rapidly read and interpret.
The VM (or JVM—Java virtual machine) is a program that executes the
code within your class files. The term “virtual machine” derives from the fact
that it acts as if it were a complete platform, or operating system, from the
standpoint of your Java programs. Your code does not make direct calls to
the operating system application programming interface (API), as it might if
you were programming directly for Windows in a language such as C or
C++.
Instead, you write code only in the Java language and use only a set of li-
braries, known as class libraries, or APIs, provided as part of the Java SDK.
You might see references to the VM as an interpreter, since that is its chief re-
sponsibility: The VM interprets the needs of your code and dispatches these
needs to the underlying operating system as necessary. The VM is also re-
sponsible for allocating and controlling any memory that your code needs.
Java is known as an object-oriented programming language. The basic
premise of an object-oriented programming language is that you can create
abstractions of real-world things, or objects, in code. For example, you can
create a Java code representation of a calculator that emulates some of the
characteristics and behavior of a real-world calculator.
If you have never programmed before, you are in luck: Object-oriented
(OO) programming is much easier to learn from scratch than if you have al-
ready learned a non-OO programming language like Cobol or C. If you have
been tainted by a procedural or declarative language, you may want to read
this chapter a few times. Expect that learning OO is not easy; expect to spend
a few months before the light bulb in your head stays on all the time without
blinking.
WHAT IS AN OBJECT? 13
Why OO?
Object-oriented programming has been around for quite some time—since
the 1960s—but only during the 1990s did it begin to truly take off in terms
of acceptance. Much of what we know about how to program well with ob-
jects has been discovered in the past decade. The most important thing we
have learned is that OO, done properly, can improve your ability to manage
and maintain software applications as they mature and grow.
What Is an Object?
An object is a code-based abstraction of some relevant concept. If you are
building a payroll system, an object could be a programming representation
of a check, an employee’s rate classification, or a check printer. It could also
be an abstraction of an activity, such as a routing process to ensure that em-
ployees are directed to the appropriate processing queue. Abstraction is best
defined as “amplification of the essential, elimination of the irrelevant.”2
A check object might contain many details such as check number, payee,
and amount. But the payroll system is not interested in things such as the size
and color of the check, so you would not represent these things in payroll
code. You might consider them for the check printing system.
There is some value to using objects that have real-world relevancy, but
you must be careful not to take the real world too far into your code. As an
example, a payroll system might have an employee object, containing details
such as an employee ID and his salary. You also want to ensure that you can
give raises to an employee. A real-world design might suggest that the code
for giving raises belongs elsewhere, but in fact the most appropriate solution
in an OO system is for the employee object to execute the code to apply a
raise. (“What? An employee giving himself a raise??”) In Agile Java, you will
discover how to make these sorts of design decisions.
2
[Martin2003].
14 AN AGILE OVERVIEW
:Security :DoorController
Door
location
secure
What Are
release
Classes?
The OO security system might define a Door class. The Door class speci-
fies that all Door objects must provide secure and release (unlock) behavior.
Furthermore, each Door object should retain its own location. A picture of
this Door class is shown in Figure 2.
The class box shows the name of the class in the first (topmost) compart-
ment. The second compartment lists the attributes—the information that
memory
address
0x0000
:Door
“3rd floor starwell”
0x0100
:Door
0x0200 “Rear entry”
0x0300
0x0400
:Door
“Operations #5”
0x0500
each Door object will store. The third compartment lists the behaviors that
each Door object supports—the messages that each door will respond to.
In an object-oriented programming language, you use the Door class as a
basis for creating, or instantiating, new objects. Each object that you instanti-
Why UML? ate is allocated a unique area in memory. Any changes you make to an object
are exclusive of all other objects in memory.
A class diagram is used to represent the structure of an object-oriented
system. It shows how classes relate to, or associate with, each other. A class
diagram can provide a quick visual understanding of a system. You can also
use it as one of the primary indicators of the quality of a system’s design.
The basis of all relationships in a class diagram is the association. An asso-
ciation between two classes is formed when one class is dependent on the
other class or when both classes are dependent on each other. Class A is de-
pendent on class B if class A works only when class B is present.
A main reason class A becomes dependent upon class B is that class A
sends one or more messages to class B. Since the Security object sends a mes-
sage to the DoorController object, the Security class is dependent upon the
DoorController class.
You show a one-way dependency between two classes with a navigable as-
sociation (see Figure 4).
Why UML?
This book uses the UML (Unified Modeling Language) standard to illustrate
some of the code. The class boxes shown in earlier sections use UML. The
UML is the de facto standard for modeling object-oriented systems. The chief
benefit of using UML is that it is universally understood and can be used to
quickly express design concepts to other developers.
UML itself is not a methodology; it is a diagramming language. It is a tool
that can be used to support documenting any kind of object-oriented system.
UML can be used on projects using virtually any methodology.
This book will explain the rudiments of the UML it uses. A good place to
obtain a better understanding of UML is the book UML Distilled, by Martin
Security DoorController
Fowler.3 Also, you can find the latest version of the UML specification online
at http://www.omg.org/technology/documents/formal/uml.htm.
Most of the UML diagrams in this book show minimal detail. I usually
omit attributes, and I only show behaviors when they are particularly rele-
vant. By and large, I intend for the UML to give you a quick pictorial repre- What Is
Inheritance?
sentation of the design of the system. The UML representation is not the
design—it is a model of the design.4 The design is the code itself.
To the extent that UML helps you understand the design of the system, it
is valuable and worthwhile. Once it begins duplicating information that is
more readily understood by reading code, UML becomes a burdensome, ex-
pensive artifact to maintain. Use UML judiciously and it will be an invalu-
able tool for communication.
What Is Inheritance?
A brief discussion of an object-oriented concept known as inheritance may
help you to understand one of the concepts in the first lesson. Inheritance is a
relationship between classes in the system that allows for one class to use an-
other class as a basis for specialization of behavior.
In our real-world example, doors are a class of things that you can shut
and open and use as entry/exit points. Beyond that commonality, you can
specialize doors: There are automatic doors, elevator doors, bank vault
doors, and so on. These are all still doors that open and close, but they each
provide a bit of additional specialized behavior.
In your security system, you have the common Door class that specifies se-
cure and release behavior and allows each Door object to store a location.
However, you must also support specialty doors that provide additional be-
haviors and attributes. An AlarmDoor provides the ability to trigger an audi-
ble alarm when activated and is also able to operate as a secure door. Since
the secure and release behaviors of the AlarmDoor are the same as those in
the Door class, the AlarmDoor can designate that it is inheriting from the
Door class and need only provide specification for alarm activation.
Inheritance provides the AlarmDoor class with the ability to reuse the se-
cure and release behaviors without having to specify them. For an Alarm-
Door object, these behaviors appear as if they were defined in the
AlarmDoor class.
3
See [Fowler2000].
4
[Martin2003/Reeves1992].
18 AN AGILE OVERVIEW
Door
location
secure
Why Test-Driven
release
Development?
AlarmDoor
activateAlarm
Figure 5 Inheritance
Note that the relationship between AlarmDoor and Door is also a naviga-
ble association. AlarmDoor is dependent upon Door—it cannot exist without
Door, since AlarmDoor inherits its code behavior and attributes from Door.
UML shows the inheritance relationship as a directional arrow with the ar-
rowhead closed instead of opened.
In the first lesson, you’ll use inheritance to take advantage of a test frame-
work. In a later lesson, you’ll learn about inheritance in greater depth.
5
[Langr2001].
6
The test verifies a functional unit of your code. Another term for unit test is pro-
grammer test.
WHY TEST-DRIVEN DEVELOPMENT? 19
7
[Beck1998].
This page intentionally left blank
Setting Up
Software You’ll
Need
21
22 SETTING UP
Java
You need version 5.0 of the Java 2 SDK (Software Development Kit) in order
to run all the examples in Agile Java. If you are running an IDE, it may al-
ready include the SDK. I still recommend installing the SDK, as it is wise to
learn how to build and execute Java programs exclusive of an IDE.
You can download the SDK from http://java.sun.com. You will also want
to download the corresponding documentation from the same site. There are
installation instructions located at the site to install the SDK that you will Software You’ll
Need
want to follow.
Note that you will have the option of downloading either the SDK or the
JRE (Java Runtime Environment). You want to download the SDK (which
includes the JRE).
The JRE is in essence the JVM. You might download and install the VM if
you are interested only in executing Java applications on your system, not in
building Java applications. The JVM is supplied separately so that you can
ship a minimal set of components with your application when you deploy it
to other machines.
After you install the SDK, you will want to extract the documentation,
which is a voluminous set of web pages stored in a subdirectory named doc.
The best place to extract these is into the directory in which you installed the
SDK (under Windows, this might be c:\Program Files\Java\jdk1.5.0). Once you
have extracted the docs, you may want to create a shortcut in your web
browser to the various index.html files located within. Minimally, you will want
to be able to pull up the API documentation, located in the api folder (sub-
directory), which is contained directly within the doc folder.
Finally, if you intend to compile and execute Java programs from the com-
mand line, you will need to update your path to include the bin directory lo-
cated directly within the JDK install directory. You will also want to create
an environment variable named JAVA_HOME that points to the installation direc-
tory of the JDK (if one was not already created by installation of the JDK).
Refer to an operating system guide for information on how to set the path and
environment variables.
java -version
24 SETTING UP
then you have not set your path correctly. To view the path under Windows,
enter the command:
path
You should see the directory in which you installed Java appear on the
path. If you do not, try restarting the shell or command prompt.
If you see a different version of Java, perhaps 1.3 or 1.4, another version
of Java appears in the path before the 1.5 version. Try moving your 1.5 in-
stall directory to the head of the path.1
You will also want to test that you properly set the JAVA_HOME environment
variable. Try executing (under Windows):
"%JAVA_HOME%"\bin\java -version
The quotes handle the possibility that there are spaces in the path name. If
you don’t get a good result, view the value of the JAVA_HOME environment vari-
able. Unix:
echo $JAVA_HOME
Windows:
echo %JAVA_HOME%
1
Windows puts the latest version of Java in the Windows SYSTEM32 directory. You
might consider (carefully) deleting this version.
SOFTWARE YOU’LL NEED 25
If you can’t get past these steps, go no farther! Seek assistance (see the sec-
tion Still Stuck? at the end of this chapter).
JUnit
You may need to download and install JUnit in order to get started. JUnit is a
simple unit-testing framework that you will need in order to do TDD.
Most major IDEs provide direct or indirect support for JUnit. Many IDEs Software You’ll
ship with the JUnit product already built in. If you are building and execut- Need
ing Java applications from the command line or as an external tool from a
programmer’s editor, you will need to install JUnit.
You can download JUnit for free from http://www.junit.org. Installation is
a matter of extracting the contents of the downloaded zip file onto your hard
drive.
Most important, the JUnit zip file contains a file named junit.jar. This file
contains the JUnit class libraries to which your code will need to refer in
order for you to write tests for your programs.
I built the tests in this book using JUnit version 3.8.1.
Ant
You may also need to download and install Ant. Ant is an XML-based build
utility that has become the standard for building and deploying Java
projects.
Most major IDEs provide direct or indirect support for Ant. Many IDEs
ship with the Ant product already built in. If you are building and executing
Java applications from the command line or as an external tool from a pro-
grammer’s editor, you will want to install Ant.
Ant is available for download at http://ant.apache.org. As with JUnit, in-
stallation is a matter of extracting the contents of the downloaded zip file
onto your hard drive.
You can choose to defer the installation of Ant until you reach Lesson 3,
the first use of Ant. This lesson includes a brief introduction to Ant.
If you are not using an IDE with built-in Ant support: You will want to in-
clude a reference to the bin directory of your Ant installation in your path
statement. Refer to an operating system guide for information on how to set
the path. You will also need to create an environment variable named ANT_HOME
that points to the installation directory of Ant. Refer to an operating system
guide for information on how to set the path and environment variables.
I built the examples in this book using Ant version 1.6.
26 SETTING UP
Does It Work?
To get started, you will code the “Hello World” program. The hello world
program is the prototypical first Java program. When executed, it prints the
text “hello world” onto your console. Many programmers venturing into
new languages create a hello world program to demonstrate the basic ability
to compile and execute code in that new language environment.
Does It Work?
javac provides numerous command line options. You can obtain a list of the op-
tions by entering the command javac and pressing enter. All options are of course
optional. Options precede the name of the source file (or files) being compiled. An
example javac command:
javac -g:none Hello.java
In this command, the text -g:none is an option that tells the compiler to generate no
debugging information (information used to help you decipher execution
problems).
You can always issue the command:
java -version
with no source file, to see which version of the Java compiler you are executing.
Save this using the filename Hello.java. Case is important in Java. If you
save it as hello.java, you may not be able to compile or execute the code.2
2
Windows is a little less picky about case. You may find that Windows doesn’t always
complain about case when it should.
EXECUTING HELLO WORLD 27
See the sidebar for more information about the javac command.
Executing Hello
You should be in the directory where you saved Hello.java. If all went World
well, you will see no messages from the compiler. If you mistyped something,
you will likely receive compilation errors. For example, you might see:
Hello.java:4: ';' expected
}
^
1 error
This specific error means that you forget to type the semicolon (;) at the
end of the third line of text.
You might see multiple errors, depending on what you mistyped. Correct
any errors and try to compile the file again. If you still have problems, make
sure your code looks exactly as it appears above. Finally, see the section Still
Stuck? at the end of this chapter.
A successful compilation of Hello.java will produce the file Hello.class.
Execute a dir or ls command to produce a directory listing and ensure that
Hello.class exists. Hello.class is the binary compiled version of the source
code—the class file. Hello.class is what the Java VM will read and interpret
in order to execute the code you typed in Hello.java.
3
Some IDEs require you to explicitly trigger a build. Other IDEs, such as Eclipse, au-
tomatically compile the necessary source files each time you save an edit change.
28 SETTING UP
or
java: Command not found.
then you either have not installed Java or the Java bin directory is not part of
Still Stuck? your path statement. Refer to the section Checking Your Java Installation for
some assistance.
• If you see:
Exception in thread "main" java.lang.NoClassDefFoundError: hello (wrong name: Hello) ...
then you entered the .class portion of the name. Correct the command and try
again.
Each time you compile the source file, the Java compiler will replace any
associated class file that already exists.
Only where pertinent in this book (for example, when I discuss classpath)
do I demonstrate use of the javac and java commands. I highly recommend that
you familiarize yourself with command-line compilation and execution. The
Java compiler and VM work virtually the same across all platforms. If you
have difficulty compiling and executing code within your IDE, you will have
to refer to the documentation for your IDE.
Still Stuck?
Getting your first Java program compiled and executed can be frustrating, as
there are many things that can go wrong. Unfortunately, I can’t cover all pos-
sibilities in this book. If you are still having trouble, there are wonderful
places to go on the web that will provide you with all the help you need.
Sun’s official Java web site (http://java.sun.com) provides you with a wealth
of information right from the source.
STILL STUCK? 29
Getting Started
Most of the lessons in the first half of Agile Java involve the development of
Testing
various pieces of a student information system. You will not build a complete
system, but you will work on various subsystems that might be part of a
complete system.
The student information system involves many different aspects of run-
ning a school or university: registration, grades, course scheduling, billing,
records, and so on.
In Lesson 1, you will:
• create a simple Java class
• create a test class that exercises the Java class
• use the JUnit framework
• learn about constructors
• refactor the code that you write
This lesson is very detail oriented. I will explicitly describe the steps you
should take in doing test-driven development. Future lessons will assume that
you are following the cycle of test-driven development in order to produce
appropriate tests and code.
Testing
Test-driven development means that you will write tests for virtually every
bit of code. It also means that you will write the tests first. The tests are a
means of specifying what the code needs to do. After writing the correspond-
ing code, the tests are run to ensure that the code does what the tests specify.
31
32 GETTING STARTED
StudentTest Student
Each class you code will have a corresponding test class. In Figure 1.1,
StudentTest is the test class for the production class Student.
StudentTest will have to create objects of class Student, send messages to
these objects, and prove that once all the messages have been sent, things are
as expected. StudentTest is thus dependent on Student, as shown by the navi-
Design gable association in the diagram. Conversely, Student is not dependent on
StudentTest: The production classes you build should know nothing about
the tests written for them.
Design
You design and construct a system based on customer needs, or requirements.
Part of the design process is translating the customer requirements into rough
ideas or sketches of how the system will be used. For a web-based system, this
means designing the web pages that will provide the application’s functional-
ity. If you are building “inbetween” software known as middleware, your sys-
tem will be used by other client software and will interact with other server
software. In this case, you will want start by defining the communication
points—the interfaces—between the other systems and the middleware.
You start by building only high-level designs, not highly detailed specifica-
tions. You will continually refine the design as you understand more about
the customer needs. You will also update the design as you discover what
works well and what doesn’t work well in the Java code that you build. The
power of object-oriented development can allow you this flexibility, the abil-
ity to quickly adapt your design to changing conditions.
Designing the system from the outside in as described above would be a
daunting task without complete understanding of the language in which
you’re going to build it—Java. To get started, you will construct some of the
internal building blocks of the system. This approach will get you past the
language basics.
The student information system is primarily about students, so as
your first task you will abstract that real-world concept into its
object-oriented representation. A candidate class might be Student.
A SIMPLE TEST 33
A Simple Test
A Simple Test
To represent the initial need to capture student information, start by creating
a class that will act as your test case. First, create a new directory or folder
on your machine.1 Then create a file named StudentTest.java in this direc-
tory. For the time being, you will save, compile, and execute code out of this
single directory. Type the following code into your editor:
public class StudentTest extends junit.framework.TestCase {
}
1
The instructions in this lesson are geared toward command-line use of Java. If you
are using an IDE, you will create a class named StudentTest in something called the
“default package.” If you are prompted for any package name, do not enter any-
thing.
34 GETTING STARTED
junit.framework.TestCase
StudentTest Student
From the command line, you can specify the classpath at the same time
that you compile the source file.
javac -classpath c:\junit3.8.1\junit.jar StudentTest.java
You must specify either the absolute or relative location of the file JUnit. jar;2
you will find it in the directory where you installed JUnit. This example spec-
ifies the absolute location of JUnit.jar.
You should also ensure that you are in the same directory as
StudentTest.java.
If you omit the classpath, the Java compiler will respond with an error
message:
JUnit
StudentTest.java:1: package junit.framework does not exist
public class StudentTest extends junit.framework.TestCase {
^
1 error
Your IDE will allow you to specify the classpath somewhere in the current
project’s property settings. In Eclipse, for example, you specify the classpath
in the Properties dialog for the project, under the section Java Build Path,
and in the Libraries tab.
JUnit
Once you have successfully compiled StudentTest, you can execute it in
JUnit. JUnit provides two GUI-based interfaces and a text interface. Refer to
the JUnit documentation for further information. The following command
will execute the AWT interface3 against StudentTest.class, using JUnit’s class
named junit.awtui.TestRunner.
java -cp .;c:\junit3.8.1\junit.jar junit.awtui.TestRunner StudentTest
2
An absolute location represents the explicit, complete path to a file, starting from
the drive letter or root of your file system. A relative location contains a path to a file
that is relative to your current location. Example: If StudentTest is in /usr/src/student
and JUnit.jar is in /usr/src/JUnit3.8.1, you can specify the relative location as
../JUnit/3.8.1/JUnit.jar.
3
The AWT is Java’s more bare-boned user interface toolkit, in contrast with Swing,
which provides more controls and features. The AWT version of the JUnit interface is
simpler and easier to understand. To use the Swing version, use the class name
junit.swingui.TestRunner instead of junit.awtui.TestRunner. To use the text version,
use the class name junit.textui.TestRunner.
36 GETTING STARTED
You once again specify the classpath, this time using the abbreviated key-
word -cp. Not only does the Java compiler need to know where the JUnit
classes are, but the Java VM also needs to be able to find these classes at run-
time so it can load them up as needed. In addition, the classpath now con-
tains a ., representing the current directory. This is so Java4 can locate
StudentTest.class: If a directory is specified instead of a JAR filename, Java
scans the directory for class files as necessary.
The command also contains a single argument, StudentTest, which you pass
to the junit.awtui.TestRunner class as the name of the class to be tested.
When you execute the TestRunner, you should see a window similar to
Figure 1.3.
JUnit
4
You’ll note I use phrases such as “Java does this” often. This is a colloquial (i.e.,
lazy) way of saying “The Java virtual machine does this,” or “The Java compiler does
that.” You should be able to determine whether I’m talking about the VM or the
compiler from the context.
ADDING A TEST 37
There really isn’t very much to the JUnit interface. I will discuss only part
of it for now, introducing the remainder bit by bit as appropriate. The name
of the class being tested, StudentTest, appears near the top in an entry field.
The Run button to its right can be clicked to rerun the tests. The interface
shows that you have already executed the tests once. If you click the Run
button (go ahead!), you will see a very quick flash of the red bar5 spanning
the width of the window.
The fact that JUnit shows a red bar indicates that something went wrong.
The summary below the red bar shows that there is one (1) failure. The Er-
rors and Failures list explains all the things that went wrong; in this case,
JUnit complained because there were “No tests found in StudentTest.” Your
job as a test-driven programmer will be to first view errors and failures in Adding a Test
JUnit and then quickly correct them.
Adding a Test
Edit the source for your StudentTest class to look like the following code:
public class StudentTest extends junit.framework.TestCase {
public void testCreate() {
}
}
The new second and third lines define a method within the StudentTest
class:
public void testCreate() {
}
A method is a block of code that will contain any number of code state-
ments. Like the class declaration, Java uses braces to delineate where the
method starts and where it ends. All code that appears between method
braces belongs to the method.
The method in this example is named testCreate. It is designated as being
public, another requirement of the JUnit testing framework.
Methods have two general purposes. First, when Java executes a method,
it steps through the code contained within the braces. Among other things,
method code can call other methods; it can also modify object attributes. Sec-
ond, a method can return information to the code that executed it.
The testCreate method returns nothing to the code that invoked it—JUnit has
no need for such information. A method that returns no information provides
5
If you are color-blind, the statistics below the bar will provide you with the informa-
tion you need.
38 GETTING STARTED
what is known as a void return type. Later (see Returning a Value from a
Method in this lesson) you will learn how to return information from a method.
The empty pair of parentheses () indicates that testCreate takes no argu-
ments (also known as parameters)—it needs no information passed to it in
order to do its work.
The name of the method, testCreate, suggests that this is a method to be used
for testing. However, to Java, it is just another method name. But for JUnit to
recognize a method as a test method, it must meet the following criteria:
• the method must be declared public,
• the method must return void (nothing),
Adding a Test
• the name of the method must start with the word test, in lowercase let-
ters, and
• the method cannot take any arguments ().
Compile this code and rerun JUnit’s TestRunner. Figure 1.4 shows that
things are looking better.
The green bar is what you will always be striving for. For the test class
StudentTest, JUnit shows a successful execution of one test method (Runs: 1)
and no errors or failures.
Remember that there is no code in testCreate. The successful execution of
JUnit demonstrates that an empty test method will always pass.
Creating a Student
Add a single line, or statement, to the testCreate method:
Creating a
public class StudentTest extends junit.framework.TestCase { Student
public void testCreate() {
new Student("Jane Doe");
}
}
You place the new keyword before the name of the class to instantiate. You
then follow the class name with an argument list. The argument list contains
information the Student class requires in order to be able to instantiate a Stu-
dent object. Different classes will need different pieces of information; some
classes will not need any information at all. It is up to the designer of the
class (you, in this case) to specify what information must be supplied.
The sole argument in this example represents the name of the student,
Jane Doe. The value "Jane Doe" is a string literal. String literals represent object
instances of the predefined Java class java.lang.String. Simply put, string lit-
erals are Java’s representation of pieces of text.
Soon you will build code for the Student class. At that time, you will spec-
ify what to do with this String argument. There are a few things you can do
with arguments: You can use them as input data to another operation, you
can store them for later use and/or retrieval, you can ignore them, and you
can pass them to other objects.
When the Java VM executes the new operator in this statement, it allocates
an area in memory to store a representation of the Student object. The VM
40 GETTING STARTED
uses information in the Student class definition to determine just how much
memory to allocate.
Note the position of the caret (^) beneath the statement in error. It indi-
cates that the Java compiler doesn’t know what the source text Student repre-
sents.
The compile error is expected. Compilation errors are a good thing—they
provide you with feedback throughout the development process. One way
you can look at a compilation error is as your very first feedback after writ-
ing a test. The feedback answers the question: Have you constructed your
code using proper Java syntax so that the test can execute?
The batch file does not execute the JUnit test if the compiler returns any compila-
tion errors. A similar script for use under Unix:
#!/bin/sh
javac -classpath "/junit3.8.1/junit.jar" *.java
if [ $? -eq 0 ]; then
java -cp ".:/junit3.8.1/junit.jar" junit.awtui.TestRunner StudentTest
fi
6
You may see different errors, depending on your Java compiler and/or IDE.
CONSTRUCTORS 41
Another option under Unix is to use make, a classic build tool available on most
systems. However, an even better solution is a tool called Ant. In Lesson 3, you
will learn to use Ant as a cross-platform solution for building and running your
tests.
To eliminate the current error, create a new class named Student.java. Edit
it to contain the following code:
class Student {
}
Run javac again, this time using a wildcard to specify that all source files Constructors
should be compiled:
javac -cp c:\junit3.8.1\junit.jar *.java
You will receive a new but similar error. Again the compiler cannot find a
symbol, but this time it points to the word new in the source text. Also, the
compiler indicates that the symbol it’s looking for is a constructor that takes
a String argument. The compiler found the class Student, but now it needs to
know what to do with respect to the "Jane Doe" String.
StudentTest.java:3: cannot find symbol
symbol : constructor Student(java.lang.String)
location: class Student
new Student("Jane Doe");
^
1 error
Constructors
The compiler is complaining that it cannot find an appropriate Student con-
structor. A constructor looks a lot like a method. It can contain any number
of statements and can take any number of arguments like a method. How-
ever, you must always name a constructor the same as the class in which it is
defined. Also, you never provide a return value for a constructor, not even
void. You use a constructor to allow an object to be initialized, often by using
other objects that are passed along as arguments to the constructor.
In the example, you want to be able to pass along a student name when
you instantiate, or construct, a Student object. The code
new Student("Jane Doe");
42 GETTING STARTED
implies that there must be a constructor defined in the Student class that
takes a single parameter of the String type. You can define such a constructor
by editing Student.java so that it looks like this:
class Student {
Student(String name) {
}
}
Local Variables
So far, when JUnit executes testCreate, Java executes the single statement that
constructs a new Student object. Once this statement executes, control re-
turns to the calling code in the JUnit framework. At this point, the object cre-
ated in testCreate disappears: It had a lifetime only as long as testCreate was
executing. Another way to say this is that the Student object is scoped to the
testCreate method.
You will want to be able to refer to this Student object later in your test.
You must somehow hold on to the memory address of where the Java VM
decided to store the Student object. The new operator returns a reference to
the object’s location in memory. You can store this reference by using the
assignment operator, represented in Java as the equals sign (=). Modify
StudentTest:
When the Java VM executes this statement, it executes the code to the
right-hand side of the assignment operator (=) first, creating a Student object
in memory. The VM takes note of the actual memory address where it places
the new Student object. Subsequently, the VM assigns this address to a refer-
ence on the left-hand side, or first half, of the statement.
The first half of the statement creates a local variable reference named
student of the type Student. The reference will contain the memory address of
the Student object. It is local because it will exist only for the duration of the
test method. Local variables are also known as temp variables or temporary
variables.
You could have named the variable someStudent, or janeDoe, but in this case,
the generic name student will work just fine. See the section Naming Conven- Local Variables
tions at the end of this lesson for guidelines on how to name your variables.
A conceptual pictorial representation of the student reference and Student
object might look something like Figure 1.5.7 This picture exists only to give
you an understanding of what’s happening behind the scenes. You need not
learn how to create your own such pictures.
Behind the scenes, Java maintains a list of all the variables you define and
the memory location to which each variable refers. One of the beauties of
Java is that you do not have to explicitly code the mundane details of creat-
ing and releasing this memory space. Developers using an older language
such as C or C++ expend a considerable amount of effort managing memory.
In Java, you don’t have to worry as much about managing memory. But it
is still possible to create an application with memory “leaks,” where the
0x0000
0x0100
0x0300
7
The memory addresses are shown in hexadecimal, which is indicated by the “0x”
that precedes each number. Lesson 10 includes a discussion of hexadecimal numbers.
44 GETTING STARTED
application continues to use more and more memory until there is none left.
It is also possible to create applications that behave incorrectly because of a
poor understanding of how Java manages memory for you. You must still
understand what’s going on behind the scenes in order to master the lan-
guage.
I will use the conceptual memory diagrams only in this chapter to help you
understand what Java does when you manipulate objects. They are not a
standard diagramming tool. Once you have a fundamental understanding of
memory allocation, you can for the most part put the nuts and bolts of it out
of mind when coding in Java.
Recompile all source files and rerun your test. So far, you haven’t written
Returning a any code that explicitly tests anything. Your tests should continue to pass.
Value from
a Method
student.getName();
You also must specify the message receiver—the object to which you want
the message sent. To do so, you first specify the object reference, student, fol-
RETURNING A VALUE FROM A METHOD 45
lowed by a period (.), followed by the message getName(). The parentheses indi-
cate that no arguments are passed along with the message. For this second
statement to work, you will need to define a corresponding method in the
Student class called getName().
The left hand of the second statement assigns the memory address of this
returned String object to a String local variable called studentName. For this
assignment to work, you’ll need to define the getName() method in Student so
that it returns a String object.
You’ll see how to build getName() in just a minute.
Compile all source files. The remaining compilation error indicates that
the compiler doesn’t know about a getName method in the Student class.
Returning a
StudentTest.java:4: cannot find symbol Value from a
symbol : method getName() Method
location: class Student
String studentName = student.getName();
^
1 error
You can eliminate this error by adding a getName method to the Student class
definition:
class Student {
Student(String name) {
}
String getName() {
}
}
You saw earlier how you can indicate that a method returns nothing by
using the void keyword. This getName method specifies instead a return type of
String. If you now try to compile Student.java, you receive an error:
Student.java:5: missing return statement
}
^
1 error
Since the method specifies a return type of String, it needs a return state-
ment that provides a String object to be returned to the code that originally
sent the getName message:
class Student {
Student(String name) {
}
String getName() {
return "";
}
}
46 GETTING STARTED
The return statement here returns an empty String object—a String with
nothing in it. Compile the code again; you should receive no compilation er-
rors. You are ready to run the test in JUnit again.
Assertions
You now have a complete context for testCreate: The first test statement cre-
ates a student with a given name, and the second statement asks for the name
from the student object. All that you need do now is add a statement that
Assertions
will demonstrate that the student name is as expected—that it is the same as
the name passed to the constructor of the student.
The third line of code (statement) is used to prove, or assert, that every-
thing went well during the execution of the first two statements. This is the
“test” portion of the test method. To paraphrase the intent of the third state-
ment: You want to ensure that the student’s name is the string literal "Jane
Doe". In more general terms, the third statement is an assertion that requires
the first argument to be the same as the second.
This third line is another message send, much like the right-hand side of
the second statement. However, there is no receiver—to what object is the
assertEquals message sent? If you specify no receiver, Java assumes that the cur-
rent object—the object in which the current method is executing—is the re-
ceiver.
The class junit.framework.TestCase includes the definition for the method
assertEquals. Remember that in your class definition for StudentTest, you de-
clared:
JUnit shows a red bar and indicates one failure. It also tells you what went
wrong in the first listbox:
Failure: testCreate(StudentTest): expected:<Jane Doe> but was:<>
The method failing is testCreate, located in the StudentTest class. The prob-
lem is that something expected the String literal "Jane Doe" but received instead
an empty String. Who is expecting this and where? JUnit divulges this infor-
mation in the second listbox, showing a walkback (also known as a stack
trace) of the code that led up to the failure. The first line of this stack trace
indicates that there was a ComparisonFailure, and the second line tells you
that the failure occurred at line 5 in StudentTest.java.
Assertions Use your editor to navigate to line 5 of the StudentTest source code. This
will show that the assertEquals method you coded is the source of the compari-
son failure:
assertEquals("Jane Doe", studentName);
class Student {
Student(String name) {
}
String getName() {
return "Jane Doe";
}
}
Recompile and rerun JUnit. Success! (See Figure 1.7.) There’s something
satisfying about seeing that green bar. Press the Run button again. The bar
should stay green. Things are still good—but not that good. Right now, all
students will be named Jane Doe. That’s not good, unless you’re starting
an all-woman school that only accepts people with the name Jane Doe.
Read on.
8
Specifically, this example compares a String object to a String variable, or reference.
Java dereferences the variable to obtain the String object at which it points and uses
this for the comparison.
INSTANCE VARIABLES 49
Instance
Variables
Instance Variables
You have built your first test and corresponding class. You used StudentTest
to help you build the Student class in an incremental manner. The test can
also be used to ensure that any future changes you make don’t break some-
thing you already did.
Unfortunately, the code isn’t quite right. If you were to build more stu-
dents, they would all respond to the getName message by saying they were Jane
Doe. You’ll mature both StudentTest and Student in this section to deal with
the problem.
You can prove the theory that every Student object will answer the name
Jane Doe by elaborating on the testCreate method. Add code to create a second
student object:
50 GETTING STARTED
0x0000
0x0100 :Student
“Joe Blow”
variable address
0x0200 :Student student 0x020F
“Jane Doe”
secondStudent 0x0100
0x0300
9
Append an & to the command, under most systems
10
Prepend the command with the word start. For example:
11
For this to work, ensure that the checkbox labeled “Reload classes every run” is
checked in the JUnit GUI.
INSTANCE VARIABLES 51
Sure enough, since getName always returns "Jane Doe", the second assertEquals state-
ment fails.
The problem is that you passed the student name to the constructor of the
Student class, but the constructor does nothing with that name. Your code in
the Student class is responsible for storing the name if anything is going to be
able to refer to the name later.
You want the student’s name to be an attribute of the student—a piece of
information that is kept by the student as long as the student object is
Instance
around. The most direct way to represent an attribute in Java is by defining it Variables
as a field, also known as an instance variable. You declare a field within the
braces that delineate the start and end of a class. A field declaration can ap-
pear anywhere within the class as long as it appears outside of the methods
defined in that class. But according to convention, you should place field dec-
larations near either the start or the end of the class.
Like a local variable, a field has a type. Define the field myName to be of the
type String within the Student class as shown here:
class Student {
String myName;
Student(String name) {
}
String getName() {
return "Jane Doe";
}
}
Student(String name) {
myName = name;
}
Finally, return the field from the getName method (instead of the String literal
"Jane Doe").
String getName() {
return myName;
}
JUnit should still be open, showing the comparison failure and red bar.
Rerun the tests by pressing the Run button. The bar should turn green.
52 GETTING STARTED
The test demonstrates, and thus documents, how Student instances can be
created with discrete names. To further bolster this assertion, add a line to
Summarizing the end of the test to ensure that you can still retrieve the proper student
the Test name from the first student object created:
public void testCreate() {
Student student = new Student("Jane Doe");
String studentName = student.getName();
assertEquals("Jane Doe", studentName);
Note that instead of assigning the result of the message send—the result of
calling student.getName()—to a local variable, you instead substituted it directly
as the second parameter of assertEquals.
Rerun the test (after compiling). The success of the test shows that student
and secondStudent refer to two distinct objects.
Refactoring
One of the main problems in software development is the high cost of main-
taining code. Part of the reason is that code quickly becomes “crufty” or
messy, as the result of rushed efforts or just plain carelessness. Your primary Refactoring
job in building software is to get it to work, a challenge that you will resolve
by writing tests before you write the code. Your secondary job is to ensure
that the code stays clean. You do this through two mechanisms:
1. ensure that there is no duplicate code in the system, and
2. ensure that the code is clean and expressive, clearly stating the in-
tent of the code
Throughout Agile Java, you will pause frequently to reflect on the code
just written. Anything that does not demonstrate adherence to these two sim-
ple rules must be reworked, or refactored, immediately. Even within a “per-
fect” design—an unrealistic goal—the poor implementation of code can
cause costly headaches for those trying to modify it.
The more you can continually craft the code as you go, the less likely you
are to hit a brick wall of code so difficult that you cannot fix it cheaply. Your
rule of thumb should be to never leave the code in a worse state than when
you started working on it.
Even within your small example so far, there is some less-than-ideal code.
Start on the path to cleaning up the code by taking a look at the test:
The first step is to eliminate the unnecessary local variables studentName and
secondStudentName.
They don’t add anything to the understanding of the method
54 GETTING STARTED
and can be replaced with simple queries to the student object, as in the very
last assertEquals.
When you are done making this change, recompile and rerun your test in
JUnit to ensure you haven’t broken anything. Your code should look like
this:
public void testCreate() {
Student student = new Student("Jane Doe");
assertEquals("Jane Doe", student.getName());
This statement creates a reference named firstStudentName of the type String and
assigns it an initial value of the String literal "Jane Doe".
The keyword final at the beginning of the statement indicates that the
String reference cannot be changed—no other object can be assigned to the
reference. You are never required to specify final, but it is considered good
form and helps document the intent that firstStudentName is acting as a constant.
You will learn other uses for final later.
Now that you have declared the constant, you can replace the String liter-
als with it.
final String firstStudentName = "Jane Doe";
Student student = new Student(firstStudentName);
assertEquals(firstStudentName, student.getName());
...
assertEquals(firstStudentName, student.getName());
Compile and rerun your test to ensure that you haven’t inadvertently bro-
ken anything.
THIS 55
The final assertEquals proves your understanding of the way Java works
rather than the functionality that you are trying to build. You would not likely
keep this assertion around in a production system. You may choose to keep it
or delete it. If you delete it, make sure you recompile and rerun your test!
Your development cycle is now:
• Write a small test to assert some piece of functionality.
• Demonstrate that the test fails.
• Write a small bit of code to make this test pass.
• Refactor both the test and code, eliminating duplicate concepts and en-
suring that the code is expressive.
This cycle will quickly become an ingrained, natural flow of development.
this
Look at the Student class code to see if it can be improved.
class Student {
String myName;
Student(String name) {
myName = name;
}
String getName() {
return myName;
}
}
56 GETTING STARTED
The code appears clean, but naming the field myName is oh so academic.
Show your professionalism by using a better name. A first thought might be
to call the field studentName. However, that introduces duplication in the form of
name redundancy—it’s clear that the field represents a student’s name, since
it is defined in the class Student.
Also, you generally name your get methods after the fields, so the redun-
dancy would become very apparent when you coded a statement like:
student.getStudentName();
class Student {
String name;
Student(String name) {
name = name;
}
String getName() {
return name;
}
}
This should pass compilation (you might see a warning). Run your test,
however, and it will fail:
junit.framework.ComparisonFailure: expected:<Jane Doe> but was:<null>
Why? Well, part of the problem is that the Java compiler allows you to
name fields the same as formal parameters, or even the same as local vari-
ables. When the code is compiled, Java tries to figure out which name you
meant. The resolution the compiler makes is to use the most locally defined
name, which happens to be the name of the formal parameter. The statement
name = name;
thus just results in the object stored in the parameter being assigned to itself.
This means that nothing is getting assigned to the instance variable (field)
called name. Field references that are not assigned an object have a special
value of null, hence the JUnit message:
expected:<Jane Doe> but was:<null>
There are two ways to ensure that the value of the formal parameter is as-
signed to the field: Either ensure both variables have different names or use
PRIVATE 57
the Java keyword this to differentiate them. Using this is the most common
approach.
The first approach means you must rename either the parameter or the
field. Java programmers use many differing conventions to solve this naming
issue. One is to rename the formal parameter so that it uses a single letter or
is prefixed with an article. For example, name could be renamed to n or aName.
Another common choice is to rename the field by prefixing it with an under-
score: _name. This makes use of the field stand out like a sore thumb, some-
thing that can be very valuable when trying to understand code. There are
other schemes, such as prefixing the argument name with the article a or an
(aName).
The second approach for disambiguating the two is to use the same name private
for both, but where necessary refer to the field by prefixing it with the Java
keyword this.
class Student {
String name;
Student(String name) {
this.name = name;
}
String getName() {
return name;
}
}
private
Java allows you to access fields of an object, just like you can call a
method:
public void testCreate() {
final String firstStudentName = "Jane Doe";
Student firstStudent = new Student(firstStudentName);
assertEquals(firstStudentName, firstStudent.getName());
58 GETTING STARTED
assertEquals(firstStudentName, firstStudent.name);
}
Suppose you want to design the Student class so that a student’s name is
Private immutable—it cannot be changed once you’ve created a Student object. The
following test code demonstrates how allowing other objects to access your
attributes can be a bad idea:
final String firstStudentName = "Jane Doe";
Student firstStudent = new Student(firstStudentName);
firstStudent.name = "June Crow";
assertEquals(firstStudentName, firstStudent.getName());
The test shows that Student client code—code that is interacting with the
Student object—can directly modify the String stored in the name instance vari-
able. While this doesn’t seem like a terrible affront, it eliminates any control
you have over clients changing your object’s data. If you want to allow client
code to change the Student’s name, you can create a method for the client to
use. For example, you could create a method called setName that takes a new
name String as parameter. Within the setName method, you could include code
for any additional control you needed.
Make the above change to StudentTest and demonstrate for yourself that
the test fails.
To protect your fields—to hide them—you should designate them as
private. Change the Student class to hide the name field.
class Student {
private String name;
...
After doing so, other code that attempts to access the field will not even
compile. Since the code in Student test refers to the name field directly:
assertEquals(firstStudentName, firstStudent.name);
Naming Conventions
You should have noticed a naming pattern in the Java code you have written
so far. Most of the elements in Java that you have already learned about—
fields, formal parameters, methods, and local variables—are named in a simi-
lar way. This convention is sometimes referred to as camel case.12 When
following the camel case naming pattern, you comprise a name, or identifier,
of words concatenated directly together. You begin each word in the identi-
fier, except for the first word, with a capital letter.
You should name fields using nouns. The name should describe what the
field is used for or what it represents, not how it is implemented. Prefixes and
suffixes that belie the type of the field are unnecessary and should be
avoided. Examples of field names to avoid are firstNameString, trim, and
sDescription.
Examples of good field names include firstName, trimmer, description, name,
mediaController, and lastOrderPlaced.
Methods are generally either actions or queries: Either you are sending a
message to tell an object to do something or you are asking an object for
some information. You should use verbs to name action methods. You
should also use verbs for query method names. The usual Java convention is
to prefix the name of the attribute being retrieved by the word get, as in
getNumberOfStudents and getStudent. I will discuss some exceptions to this rule later.
12
Imagine that the letters represent the view of a camel from the side. The upper let-
ters are the humps. For an interesting discussion of the term, see
http://c2.com/cgi/wiki?CamelCase. You may hear other names for camel case, such as
“mixed case.”
60 GETTING STARTED
13
Camel case is thus sometimes called lower camel case to distinguish it from upper
camel case.
14
A guideline known as the Single-Responsibility Principle [Martin2003].
15
I’ll bet you thought they stood for customer and number. See how easy it is to be led
astray?
WHITESPACE 61
Remember that Java is case sensitive. This means that stuDent represents a
different name than student. It is bad form to introduce this sort of confusion
in naming, however.
These naming conventions indicate that there are commonly accepted cod-
ing standards for things that aren’t necessarily controlled by the compiler.
The compiler will allow you to use hideous names that will irritate your fel-
low developers. Most shops wisely adopt a common standard to be followed
by all developers. The Java community has also come to a higher level of
agreement on such standards than, say, the C++ community. Sun’s coding
conventions for Java, located at http://java.sun.com/docs/codeconv, is a good,
albeit incomplete and outdated, starting point for creating your own coding
standards. Other style/standards books exist, including Essential Java Style 16 Whitespace
and Elements of Java Style.17
Whitespace
The layout of code is another area where you and your team should adhere
to standards. Whitespace includes spaces, tab characters, form feeds, and
new lines (produced by pressing the enter key). Whitespace is required be-
tween certain elements and is optional between others. For example, there
must be at least one space between the keyword class and the name of the
class in a class declaration:
class Student
The Java compiler ignores extra whitespace. You should use spaces, tabs, and
blank lines to judiciously organize your code. This goes a long way toward
allowing easy future understanding of the code.
The examples in this book provide a consistent, solid and generally ac-
cepted way of formatting Java code. If your code looks like the examples, it
will compile. It will also meet the standards of most Java development shops.
You will want to decide on things such as whether to use tabs or spaces for
indenting and how many characters to indent by (usually three or four).
16
[Langr2000].
17
[Vermeulen2000].
62 GETTING STARTED
Exercises
Exercises appear at the end of lesson. Many of the exercises have you build
pieces of an application that plays the game of chess. If you are unfamiliar
with the rules of chess, you can read them at http://www.chessvariants
.com/d.chess/chess.html.
1. As with Student, you will start simply by creating a class to represent
a pawn. First, create an empty test class named PawnTest. Run JUnit
against PawnTest and observe that it fails, since you haven’t written
any test methods yet.
Exercises
2. Create a test method named testCreate. Ensure that you follow the cor-
rect syntax for declaring test methods.
3. Add code to testCreate that instantiates a Pawn object. Ensure that you
receive a compile failure due to the nonexistent class. Create the Pawn
class and demonstrate a clean compile.
4. Assign the instantiated Pawn object to a local variable. Ask the pawn
for its color. Code a JUnit assertion to show that the default color of a
pawn is represented by the string "white". Demonstrate test failure, then
add code to Pawn to make the test pass.
5. In testCreate, create a second pawn, passing the color "black" to its con-
structor. Assert that the color of this second pawn is "black". Show the
test failure, then make the test pass. Note: Eliminate the default con-
structor—require that clients creating Pawn objects pass in the color.
The change will impact the code you wrote for Exercise #4.
6. In testCreate, create constants for the Strings "white" and "black". Make
sure you rerun your tests.
Lesson 2
Java Basics
CourseSession
Schools have courses, such as Math 101 and Engl 200, that are
taught every semester. Basic course information, such as the depart-
ment, the number, the number of credits, and the description of the
course, generally remains the same from semester to semester.
A course session represents a specific occurrence of a course. A
course session stores the dates it will be held and the person teaching
63
64 JAVA BASICS
it, among other things. It also must retain an enrollment, or list of students,
for the course.
You will define a CourseSession class that captures both the basic course
information and the enrollment in the session. As long as you only need to
work with the CourseSession objects for a single semester, no two Course
Sessions should need to refer to the same course. Once two CourseSession
objects must exist for the same course, having the basic course information
stored in both CourseSession objects is redundant. For now, multiple sessions
is not a consideration; later you will clean up the design to support multiple
sessions for a single course.
Create CourseSessionTest.java. Within it, write a test named testCreate.
Like the testCreate method in StudentTest, this test method will demonstrate
how you create CourseSession objects. A creation test is always a good place
to get a handle on what an object looks like just after it’s been created.
CourseSession
public class CourseSessionTest extends junit.framework.TestCase {
public void testCreate() {
CourseSession session = new CourseSession("ENGL", "101");
assertEquals("ENGL", session.getDepartment());
assertEquals("101", session.getNumber());
}
}
The test shows that a CourseSession can be created with a course depart-
ment and number. The test also ensures that the department and number are
stored correctly in the CourseSession object.
To get the test to pass, code CourseSession like this:
class CourseSession {
private String department;
private String number;
String getDepartment() {
return department;
}
String getNumber() {
return number;
}
}
INT 65
So far you’ve created a Student class that stores student data and a Course-
Session class that stores course data. Both classes provide “getter” methods
to allow other objects to retrieve the data.
However, data classes such as Student and CourseSession aren’t terribly
interesting. If all there was to object-oriented development was storing data
and retrieving it, systems wouldn’t be very useful. They also wouldn’t be
object-oriented. Remember that object-oriented systems are about modeling
behavior. That behavior is effected by sending messages to objects to get
them to do something—not to ask them for data.
But, you’ve got to start somewhere! Plus, you wouldn’t be able to write as-
sertions in your test if you weren’t able to ask objects what they look like.
Courses don’t earn any revenue for the school unless students enroll
in them. Much of the student information system will require you to
be able to work with more than one student at a time. You will want
to store groups, or collections, of students and later execute operations
against the students in these collections.
CourseSession will need to store a new attribute—a collection of Student
objects. You will want to bolster your CourseSession creation test so that it
says something about this new attribute. If you have just created a new
course session, you haven’t yet enrolled any students in it. What can you as-
sert against an empty course session?
Modify testCreate so that it contains the bolded assertion:
public void testCreate() {
CourseSession session = new CourseSession("ENGL", "101");
assertEquals("ENGL", session.getDepartment());
assertEquals("101", session.getNumber());
assertEquals(0, session.getNumberOfStudents());
}
int
The new assertion verifies that the number of students enrolled in a brand-
new session should be 0 (zero). The symbol 0 is a numeric literal that
represents the integer zero. Specifically, it is an integer literal, known in Java
as an int.
66 JAVA BASICS
(The ellipses represent the instance variables, constructor code, and getter
methods that you have already coded.) The return type of getNumberOf
Students is specified as int. The value returned from a method must match the
return type, and in this method it does—getNumberOfStudents returns an int. The
int type allows variables to be created that store integer values from
–2,147,483,648 to 2,147,483,647.
Numbers in Java are not objects like String literals are. You cannot send
messages to numbers, although numbers can be passed as parameters along
int with messages just like Strings can. Basic arithmetic support in Java is pro-
vided syntactically; for many other operations, support is provided by system
libraries. You will learn about similar non-object types later in Agile Java. As
a whole, these non-object types are known as primitive types.
You have proved that a new CourseSession object is initialized properly,
but you haven’t shown that the class can enroll students properly. Create a
second test method testEnrollStudents that enrolls two students. For each, create
a new Student object, enroll the student, and ensure that the CourseSession
object reports the correct number of students.
public class CourseSessionTest extends junit.framework.TestCase {
public void testCreate() {
...
}
How do you know that you need a method named enroll, and that it
should take a Student object as a parameter? Part of what you are doing in a
test method is designing the public interface into a class—how developers
INT 67
will interact with the class. Your goal is to design the class such that the
needs of developers who want use it are met in as simple a fashion as
possible.
The simplest way of getting the second assertion (that the number of stu-
dents is two) to pass would be to return 2 from the getNumberOfStudents method.
However, that would break the first assertion. So you must somehow track
the number of students inside of CourseSession. To do this, you will again in-
troduce a field. Any time you know you need information to be stored, you
will likely use fields to represent object state. Change the CourseSession class
to look like this:
class CourseSession {
...
private int numberOfStudents = 0;
...
int getNumberOfStudents() {
return numberOfStudents; int
}
The field to track the student count is named numberOfStudents, it is private per
good practice, and it is of the type int. It is also assigned an initial value of 0.
When a CourseSession object is instantiated, field initializers such as this ini-
tialization of numberOfStudents are executed. Field initializers are executed prior
to the invocation of code in the constructor.
The method getNumberOfStudents now returns the field numberOfStudents, instead of
the int literal 0.
Each time the enroll method is called, you should increment the number of
students by one. The single line of code in the enroll method accomplishes
this:
numberOfStudents = numberOfStudents + 1;
The + sign and many other mathematical operators are available for work-
ing with variables of the int type (as well as other numeric types to be dis-
cussed later). The expression on the right hand side of the = sign takes
whatever value is currently stored in numberOfStudents and adds 1 to it. Since the
numberOfStudents field appears to the left of the = sign, the resultant value of the
right-hand side expression is assigned back into it. Bumping up a variable’s
value by one, as in this example, is a common operation known as
68 JAVA BASICS
CourseSessionTest StudentTest
CourseSession Student
incrementing the variable. There are other ways to increment a variable that
will be discussed later.
It may seem odd that numberOfStudents appears on both sides of the assign-
ment operator. Remember that the Java VM always executes the right hand
Initialization side of an assignment statement first. It calculates a result using the expres-
sion to the right, and assigns this result to the variable on the left.
Note that the enroll method has a return type void, meaning that it returns
nothing to the message sender.
The class diagram in Figure 2.1 shows the existing structure of your sys-
tem so far.
Conceptually, a course session should be able to hold several students. In
reality—in the code—the course session holds references to no student ob-
jects. It only holds a count of students. Later, when you modify the Course
Session class to actually store Student references, the UML diagram will be
modified to show that there is a one-to-many relationship between Course
Session and Student.
CourseSession depends on Student, since the enroll method can take a Stu-
dent object as a parameter. In other words, you would not be able to compile
the CourseSession class if the Student class did not exist.
Figure 2.1 will be the last class diagram where I show every test class.
Since you are doing test-driven development, future diagrams will imply the
existence of a test class for each production class, unless otherwise noted.
Initialization
In the last section, you introduced the numberOfStudents field and initialized it to
0. This initialization is technically not required—int fields are initialized to 0
by default. Explicitly initializing a field in this manner, while unnecessary, is
useful to help explain the intent of the code.
DEFAULT CONSTRUCTORS 69
For now, you have two ways to initialize fields: You can initialize at the
field level or you can initialize in a constructor. You could have initialized
numberOfStudents in the CourseSession constructor:
class CourseSession {
private String department;
private String number;
private int numberOfStudents;
Default Constructors
You may have noted that neither of the test classes, StudentTest and Course-
SessionTest, contains a constructor. Often you will not need to explicitly ini-
tialize anything, so the Java compiler does not require you to define a con-
structor. If you do not define any constructors in a class,1 Java provides a
default, no-argument constructor. For StudentTest, as an example, it is as if
you had coded an empty constructor:
class StudentTest extends junit.framework.TestCase {
StudentTest() {
}
...
}
The use of default constructors also implies that Java views constructors
as essential elements to a class. A constructor is required in order for Java to
initialize a class, even if the constructor contains no additional initialization
1
The Java compiler does allow you to define multiple constructors in a single class;
this will be discussed later.
70 JAVA BASICS
code. If you don’t supply a constructor, the Java compiler puts it there
for you.
Suites
In the last section, you introduced a second test class, CourseSessionTest.
Going forward, you could decide to run JUnit against either CourseSessionTest
or StudentTest, depending on which corresponding production class you
changed. Unfortunately, it is very possible for you to make a change in the
Student class that breaks a test in CourseSessionTest, yet all the tests in
StudentTest run successfully.
You could run the tests in CourseSessionTest and then run the tests in
StudentTest, either by restarting JUnit each time or by retyping the test class
Suites
name in JUnit or even by keeping multiple JUnit windows open. None of
these solutions is scalable: As you add more classes, things will quickly be-
come unmanageable.
Instead, JUnit allows you to build suites, or collections of tests. Suites can
also contain other suites. You can run a suite in the JUnit test runner just like
any test class.
Create a new class called AllTests with the following code:
public class AllTests {
public static junit.framework.TestSuite suite() {
junit.framework.TestSuite suite =
new junit.framework.TestSuite();
suite.addTestSuite(StudentTest.class);
suite.addTestSuite(CourseSessionTest.class);
return suite;
}
}
If you start JUnit with the class name AllTests, it will run all of the tests
in both CourseSessionTest and StudentTest combined. From now on, run
AllTests instead of any of the individual class tests.
The job of the suite method in AllTests is to build the suite of classes to be
tested and return it. An object of the type junit.framework.TestSuite manages
this suite. You add tests to the suite by sending it the message addTestSuite. The
parameter you send with the message is a class literal. A class literal is com-
prised of the name of the class followed by .class. It uniquely identifies the
class, and lets the class definition itself be treated much like any other object.
Each time you add a new test class, you will need to remember to add it to
the suite built by AllTests. This is an error-prone technique, as it’s easy to for-
THE SDK AND JAVA.UTIL.ARRAYLIST 71
get to update the suite. In Lesson 12, you’ll build a better solution: a tool that
generates and executes the suite for you.
The code in AllTests also introduces the concept of static methods, some-
thing you will learn about in Lesson 4. For now, understand that you must
declare the suite method as static in order for JUnit to recognize it.
2
A technique whereby members of a development team work in dynamic pairs of de-
velopers who jointly construct code.
ADDING OBJECTS 73
exceptions. I will explain interfaces and exceptions later. For now, scroll
down until you see the class named ArrayList and select it.
The package java.util contains several utility classes that you will use often
in the course of Java development. The bulk of the classes in the package
support something called the Collections Framework. The Collections
Framework is a consistent sublibrary of code used to support standard data
structures such as lists, linked lists, sets, and hash tables. You will use collec-
tions over and over again in Java to work with groupings of related objects.
The main pane (to the right) shows all of the detailed information on the
class java.util.ArrayList. Scroll down until you see the Method Summary.
The Method Summary shows the methods implemented in the java.util
.ArrayList class. Take a few minutes to read through the methods available.
Each of the method names can be clicked on for detailed information regard-
ing the method.
You will use three of the java.util.ArrayList methods as part of the current
Adding Objects
exercise: add, get, and size. One of them, size, is already referenced by the test.
The following line of code asserts that there is one object in the java.util.
ArrayList object.
assertEquals(1, allStudents.size());
The next line of new code asserts that the first element in allStudents is equal to
the student that was enrolled.
assertEquals(student, allStudents.get(0));
According to the API documentation, the get method returns the element at an
arbitrary position in the list. This position is passed to the get method as an
index. Indexes are zero-based, so get(0) returns the first element in the list.
Adding Objects
The add method is documented in the Java SDK API as taking an Object as
parameter. If you click on the parameter Object in the API docs, you will see
that it is actually java.lang.Object.
You might have heard that Java is a “pure” object-oriented language,
where “everything is an object.”3 The class java.lang.Object is the mother of
all classes defined in the Java system class library, as well as of any class that
3
A significant part of the language is not object-oriented. This will be discussed
shortly.
74 JAVA BASICS
you define, including Student and StudentTest. Every class inherits from
java.lang.Object, either directly or indirectly. StudentTest inherits from junit
.framework.TestCase, and junit.framework.TestCase inherits from
java.lang.Object.
This inheritance from java.lang.Object is important: You’ll learn about
several core language features that depend on java.lang.Object. For now, you
need to understand that String inherits from java.lang.Object, as does Stu-
dent, as does any other class you define. This inheritance means that String
objects and Student objects are also java.lang.Object objects. The benefit is
that String and Student objects can be passed as parameters to any method
that takes a java.lang.Object as a parameter. As mentioned, the add method
takes an instance of java.lang.Object as a parameter.
Even though you can pass any type of object to a java.util.ArrayList via its
add method, you don’t usually want to do that. In the CourseSession object,
you know that you only want to be able to enroll Students. Hence the para-
Adding Objects
meterized type declaration. One of the benefits to restricting the
java.util.ArrayList via a parameterized type is that it protects other types of
objects from inadvertently being added to the list.
Suppose someone is allowed to add, say, a String object to the list of stu-
dents. Subsequently, your code asks for the list of students using getAll
Students and retrieves the String object from the list using the get method.
When you attempt to assign this object to a Student reference, the Java VM
will choke, generating an error condition. Java is a strongly typed language,
and it will not allow you to assign a String to a Student reference.
The following changes to CourseSession (appearing in bold) will make the
test pass:
class CourseSession {
...
private java.util.ArrayList<Student> students =
new java.util.ArrayList<Student>();
...
void enroll(Student student) {
numberOfStudents = numberOfStudents + 1;
students.add(student);
}
java.util.ArrayList<Student> getAllStudents() {
return students;
}
}
A new field, students, is used to store the list of all students. It is initialized
to an empty java.util.ArrayList object that is bound to contain only Student
INCREMENTAL REFACTORING 75
*
CourseSession Student
Student
java.util.ArrayList
objects.4 The enroll method adds the student to this list, and the getAll
Students method simply returns the list.
Incremental
Figure 2.3 shows how CourseSession is now dependent upon the parame- Refactoring
terized type java.util.ArrayList<Student>. Also, the fact that CourseSession is
dependent upon zero to many students is shown by the * at the end of the as-
sociation between CourseSession and Student.
The class diagram in Figure 2.3 is not normal—it really shows the same
information twice, in a different fashion. The parameterized type declaration
suggests that a single CourseSession has a relationship to a number of Stu-
dent objects stored in an ArrayList. The association from CourseSession to
Student similarly shows a one-to-many relationship.
Incremental Refactoring
Since java.util.ArrayList provides a size method, you can just ask the students
ArrayList object for its size instead of tracking numberOfStudents separately.
int getNumberOfStudents() {
return students.size();
}
Make this small refactoring, then recompile and rerun the test. Having the
test gives you the confidence to change the code with impunity—if the
change doesn’t work, you simply undo it and try something different.
4
Due to width restrictions, I’ve broken the students field declaration into two lines.
You can type this as a single line if you choose.
76 JAVA BASICS
String getDepartment() {
return department;
}
String getNumber() {
return number;
}
int getNumberOfStudents() {
return students.size();
}
java.util.ArrayList<Student> getAllStudents() {
return students;
}
}
Objects in Memory
In testEnrollStudents, you send the getAllStudents message to session and store the
result in a java.util.ArrayList reference that is bound to the Student class—it
can only contain Student objects. Later in the test, after enrolling the second
OBJECTS IN MEMORY 77
student, allStudents now contains both students—you don’t need to ask the
session object for it again.
0x0000
0x0100 :CourseSession
students: 0x0200
variable address
0x0200 students: ArrayList
session 0x0100
student[0]: 0x0320
student[1]: 0x0400 allStudents 0x0200
student 0x0320
0x0300
student1 0x0400
:Student
“Cain DiVoe”
:Student
0x0400
“Coralee DeVaughn”
any code using the reference ends up at the same memory location at which
the students field is stored.
(Make sure your tests still run! I’ll keep reminding you for a while.)
Update AllTests, StudentTest, and CourseSession to use import statements.
Your code will look so much cleaner!
sample or academic programs. But for any real software development you
do, all of your classes should belong to some package other than the default
package. In fact, if you place classes in the default package, you will not be
able to reference these classes from other packages.
If you are working in a modern IDE, moving a class into a package can be
as easy as dragging the class name onto a package name. If you are not work-
ing in an IDE, setting up packages and understanding related classpath issues
can be somewhat complex. Even if you are working in an IDE, it is important
to understand the relationship between packages and the underlying file sys-
tem directory structure.
Let’s try moving your classes into a package named studentinfo. Note that
package names are by convention comprised of lowercase letters. While you
should still avoid abbreviations, package names can get unwieldy fairly
quickly. The judicious use of abbreviations might help keep the package
name from being unmanageable.
The Default
Package and You should also not start your package names with java or javax, both of
the package which should be used exclusively by Sun.
Statement If your IDE supports moving classes into packages as a simple operation,
go ahead and take advantage of it. However, make sure you understand how
the classes relate to a package structure, as this will be important when you
construct deployable libraries. Consider copying the source files into a differ-
ent directory structure and following the exercise anyway.
From the directory in which your class files are located, create a new sub-
directory called studentinfo. Case is important—ensure that you name this sub-
directory using all lowercase letters. If your source files are currently located
in c:\source, then you should now have a directory named c:\source\studentinfo.
Starting with a Unix directory named /usr/src, you should have a directory
named /usr/src/studentinfo.
First carefully delete all of the class files that have been generated. Class
files, remember, end with a .class extension. Don’t lose all of your hard
work—back up your directory if you are unsure about this step. Under Win-
dows, the command:
del *.class
rm *.class
After deleting the class files, move the five source files (AllTests.java,
StudentTest.java, Student.java, CourseSessionTest.java, and CourseSesson
.java) into the studentinfo directory. You need to store classes in a directory
structure that corresponds to the package name.
THE SETUP METHOD 81
Edit each of the five Java source files. Add a package statement to each that
identifies the class as belonging to the studentinfo package. The package state-
ment must be the very first statement within a Java class file.
package studentinfo;
class Student {
...
You should be able to compile the classes from within the studentinfo sub-
directory.
However, two things will have to change when you run JUnit against
AllTests. First, the full name of the class will have to be passed into the
TestRunner, otherwise JUnit will not find it. Second, if you are in the
studentinfo directory, the TestRunner will not be able to find studentinfo.All-
Tests. You must either go back to the parent directory or, better yet, alter the
classpath to explicitly point to the parent directory. The following command The setUp
shows the altered classpath and the fully qualified test class name. Method
Caution!
It’s easy to make the mistake of coding setUp so that it declares session as a local
variable:
public void setUp() {
CourseSession session =
new CourseSession("ENGL", "101");
}
It is legal to define a local variable with the same name as an instance variable, but
it means that the instance variable session will not get properly initialized. The re-
sult is an error known as a NullPointerException, which you’ll learn about in Les-
son 4.
More Refactoring
The method testEnrollStudents is a bit longer than necessary. It contains a few
too many assertions that track the number of students. Overall, the method
is somewhat difficult to follow.
Instead of exposing the entire list of students to client code (i.e., other
code that works with CourseSession objects), you can instead ask the Course-
Session to return the Student object at a specific index. You currently have no
need for the entire list of students as a whole, so you can eliminate the getAll
Students method. This also means that you no longer have to test the size of
the ArrayList returned by getAllStudents. The test method can be simplified to
this:
public void testEnrollStudents() {
Student student1 = new Student("Cain DiVoe");
More
session.enroll(student1);
Refactoring
assertEquals(1, session.getNumberOfStudents());
assertEquals(student1, session.get(0));
Another significant benefit of this refactoring is that you have hidden a de-
tail of CourseSession that was unnecessarily exposed. You have encapsulated
the students collection, only selectively allowing the get, add, and size opera-
tions to be performed on it.
This encapsulation provides two significant benefits: First, you currently
store the list of in an ArrayList. An ArrayList is one specific kind of data
84 JAVA BASICS
structure with certain use and performance characteristics. If you expose this
list directly to client code, their code now depends upon the fact that the stu-
dents are available as an ArrayList. This dependency means that later you
cannot easily change the representation of how students are stored. Second,
exposing the entire collection means that other classes can manipulate that
collection—add new Student objects, remove Student objects, and so on—
without your CourseSession class being aware of these changes. The integrity
of your CourseSession object could be violated!
Class Constants
It is never a good idea to embed literals directly in code, as mentioned earlier.
Declaring a local variable as final, which prevents other code from reassign-
Class
Constants
ing its value, is a good idea. The final keyword tells other developers that “I
don’t intend for you to be able to change the value of this variable.”
Doing so can provide you with an additional level of protection from other
code in the method that assigns a different value to the local variable or argument.
Many developers insist on doing so, and it’s not a bad practice.
I choose not to, but I recommend that you try it and see how it helps you. My
general reason to mark fields as final is for readability, not protection. As a rule,
you should never assign values to arguments. And you should rarely reassign val-
ues to local object reference variables. I follow both of these rules and don’t feel
the need to demonstrate that fact by marking the field as final. Instead, I use final
to emphasize the fact that a local declaration is a literal constant and not just an
initialized variable. Your mileage may vary.
CLASS CONSTANTS 85
Frequently you will need more than one class to use the same literal. In
fact, if you are doing test-driven development properly, any time you create a
literal in code, it is there by virtue of some assertion in a test method against
that same literal. A test might say, “assert that these conditions produce an
error and that the error message is such-and-such a message.” Once this test
is written, you then write the production code that produces such-and-such
an error message under the right conditions. Now you have code duplica-
tion—“such-and-such an error message” appears in both the test and the
production code.
While a few duplicated strings here and there may seem innocuous, over-
looking the need to refactor out this duplication may prove costly in the fu-
ture. One possibility is related to the growth of your software. Initially you
may only deploy your system to local customers. Later, as your software
achieves more success, the business may decide that it needs to deploy the
software in another country. You will then need to internationalize your soft-
Class
ware, by deploying it with support for other languages and with considera- Constants
tion for other cultures.
Many software developers have encountered this problem. The software
they worked on for months or years has hundreds or thousands of literal
Strings strewn throughout the code. It is a major effort to retrofit interna-
tionalization support into this software.
As a craftsman of Java code, it is your job to stay vigilant and ensure that
duplication is eliminated as soon as you recognize it.5
A class constant is a field that is declared with the static and final key-
words. As a reminder, the final keyword indicates that the field reference can-
not be changed to point to a different value. The static keyword means that
the field is available for use without having to first construct an instance of
the class in which it is declared. It also means that there is one and only one
field in memory instead of one field for each object created. The following
example declares a class constant:
class ChessBoard {
static final int SQUARES_PER_SIDE = 8;
}
5
For the problem of duplicated strings, a more robust solution involving resource
bundles is more appropriate. See Additional Lesson III for a brief discussion of re-
source bundles.
86 JAVA BASICS
int numberOfSquares =
ChessBoard.SQUARES_PER_SIDE * ChessBoard.SQUARES_PER_SIDE;
You will use class constants already defined in the Java class library in the
next section on Dates. Shortly thereafter you will define your own class con-
stant in the CourseSession code.
Overloaded Dates
Constructors
If you browse through the J2SE API documentation for the package java.util,
you will see several classes that are related to times and dates, including Cal-
endar, GregorianCalendar, Date, TimeZone, and SimpleTimeZone. The Date
class provides a simple timestamp mechanism. The other, related classes
work together with Date to provide exhaustive support for internationalized
dates and for working with components of a timestamp.
Initial versions of Java shipped with the Date class as the sole provider of
support for dates and times. The Date class was designed to provide most of
the functionality needed. It is a simple implementation: Internally, a date is
represented by the number of milliseconds (thousandths of a second) since
January 1, 1970 GMT at 00:00:00 (known as the “epoch”).
Overloaded Constructors
The Date class provides a handful of constructors. It is possible and often de-
sirable to provide a developer with more than one way to construct new ob-
jects of a class type. You will learn how to create multiple constructors for
your class in this lesson.
In the Date class, three of the constructors allow you to specify time parts
(year, month, day, hour, minute, or second) in order to build a Date object
for a specific date and time. A fourth constructor allows you to construct a
date from an input String. A fifth constructor allows you to construct a date
using the number milliseconds since the epoch. A final constructor, one that
OVERLOADED CONSTRUCTORS 87
CourseSession session =
new CourseSession("ABCD", "200", startDate);
88 JAVA BASICS
year = 103;
month = 3;
date = 25;
Date sixteenWeeksOut = new Date(year, month, date);
assertEquals(sixteenWeeksOut, session.getEndDate());
}
This code uses one of the deprecated constructors for Date. Also of note
are the odd-looking parameter values being passed to the Date constructor.
Year 103? Month 0?
The API documentation, which should be your first reference for under-
standing a system library class, explains what to pass in for the parameters.
Specifically, documentation for the Date constructor says that the first para-
Overloaded meter represents “the year minus 1900,” the second parameter represents
Constructors “the month between 0 and 11,” and the third parameter represents “the day
of the month between 1-31.” So new Date(103, 0, 6) would create a Date repre-
sentation for January 6, 2003. Lovely.
Since the start date is so critical to the definition of a course session, you
want the constructor to require the start date. The test method constructs a
new CourseSession object, passing in a newly constructed Date object in ad-
dition to the department and course number. You will want to change the in-
stantiation of CourseSession in the setUp method to use this modified
constructor. But as an interim, incremental approach, you can instead supply
an additional, overloaded constructor.
The test finally asserts that the session end date, returned by getEndDate, is
April 25, 2003.
You will need to make the following changes to CourseSession in order to
get the test to pass:
import java.util.ArrayList;
import java.util.Date;
OVERLOADED CONSTRUCTORS 89
import java.util.Calendar;
import java.util.GregorianCalendar;
class CourseSession {
private String department;
private String number;
private ArrayList<Student> students = new ArrayList<Student>();
private Date startDate;
As I describe what getEndDate does, try to follow along in the J2SE API docu-
mentation for the classes GregorianCalendar and Calendar.
In getEndDate, you first construct a GregorianCalendar object. You then use
the setTime6 method to store the object representing the session start date in
the calendar. Next, you create the local variable numberOfDays to represent the
number of days to be added to the start date in order to come up with the
end date. The appropriate number is calculated by multiplying 16 weeks by
7 days per week, then subtracting 3 days (since the last day of the session is
on the Friday of the 16th week).
The next line:
calendar.add(Calendar.DAY_OF_YEAR, numberOfDays);
sends the add message to the calendar object. The add method in GregorianCal-
endar takes a field and an amount. You will have to look at the J2SE API
documentation for Calendar—in addition to the documentation for Gregorian-
Calendar—to fully understand how to use the add method. Gregorian-
6
Don’t use this as a good example of method naming!
90 JAVA BASICS
Calendar is a subclass of Calendar, which means that the way it works is tied
tightly to Calendar. The field that represents the first parameter tells the cal-
endar object what you are adding to. In this case, you want to add a number
to the day of year. The Calendar class defines DAY_OF_YEAR as well as several
other class constants that represent date parts, such as YEAR.
The calendar now contains a date that represents the end date of the
course session. You extract this date from the calendar using the getTime
method and finally return the end date of the course session as the result of
the method.
You might wonder if the getEndDate method will work, then, when the start
date is close to the end of the year. If that can possibly happen, it’s time to
write a test for it. However, the student information system you are writing is
for a university that has been around for 200 years. No semester has ever
begun in one year and ended in the next, and it will never happen. In short,
you’re not going to need to worry about it . . . yet.
Overloaded
Constructors The second constructor will be short-lived, but it served the purpose of al-
lowing you to quickly get your new test to pass. You now want to remove
the older constructor, since it doesn’t initialize the session start date. To do
so, you will need to modify the setUp method and testCourseDates. You should
also amend the creation test to verify that the start date is getting stored
properly.
package studentinfo;
import junit.framework.TestCase;
import java.util.ArrayList;
import java.util.Date;
You can now remove the older constructor from CourseSession. You’ll
also need to add the getStartDate method to CourseSession:
class CourseSession {
...
Date getStartDate() {
return startDate;
}
}
Deprecation
Warnings
Deprecation Warnings
The above code will compile and pass the tests, but you will see a warning or
note in the compilation output. If you are using an IDE, you might not see the
warnings. Find out how to turn on the warnings—you don’t want to hide them.
If you are compiling from the command line, you should do just what the
message says: Enter the compilation command again, but modify it to include
the compilation switch -Xlint:deprecation option.
javac -classpath c:\junit3.8.1\junit.jar -Xlint:deprecation *.java
7
He writes, as he sits here with a corrupt molar . . .
92 JAVA BASICS
Refactoring
Refactoring
A quick improvement is to remove the unnecessary local variable, endDate, that
appears at the end of the method getEndDate in CourseSession. Declaring the
temporary variable aided in your understanding of the Calendar class:
A more concise form is to simply return the Date object returned by the call
to getTime.
return calendar.getTime();
Import Refactorings
The CourseSession class now must import four different classes from the
package java.util:
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Calendar;
This is still reasonable, but you can see that the list could quickly get out
of hand as more system library classes are used. It is also a form of duplica-
tion—the package name is duplicated in each import statement. Remember
that your primary refactoring job is to eliminate as much duplication as
possible.
REFACTORING 93
Java provides a shortcut version of the import statement to declare that you
are importing all classes from a specified package:
import java.util.*;
This form of import is known as a package import. The asterisk (*) acts as
a wildcard character.
After making this change, you can use any additional classes from the
java.util package without having to modify or add to the import statements.
Note that there is no runtime penalty for using either this form or the singu-
lar class form of the import statement. An import statement merely declares that
classes might be used in the class file. An import statement does not guarantee
that any classes from a package are used in the class file.
There is no consensus about which form is more proper. Most shops use
the * form of the import statement either all the time or when the number of
import statements begins to get unwieldy. Some shops insist that all classes
Refactoring
must be explicitly named in the import statements, which makes it easier to de-
termine the package a class comes from. Modern Java IDEs can enforce the
import convention your shop decides on and even switch back and forth be-
tween the various forms. An IDE makes the choice of which form to use less
significant.
It is possible to import a class or package but not use the class or any
classes from the package within your source code. The Java compiler will not
warn you about these unnecessary import statements. Most modern IDEs have
optimizing facilities that will help you remove them.
Calendar class. However, the code is somewhat confusing because the year
has to be specified as relative to 1900, and the months have been numbered
from 0 through 11 instead of 1 through 12.
In CourseSessionTest, code a new method called createDate that takes more
sensible inputs.
This will allow you to create dates using a 4-digit year and a number from 1
through 12 for the month.
You can now refactor setUp and testCourseDates to use this utility method.
With the introduction of the utility method, defining the local variables year,
month, and date adds little to the understanding of the code, since the factory
method8 createDate encapsulates some of the confusion. You can eliminate the
Refactoring
local variables and embed them directly as parameters in the message send to
createDate:
Some of you may be shaking your head at this point. You have done a lot
of work, such as introducing local variables, and then you have undone
much of that same work shortly thereafter.
Part of crafting software is understanding that code is a very malleable
form. The best thing you can do is get into the mindset that you are a sculp-
tor of code, shaping and molding it into a better form at all times. Once in a
while, you’ll add a flourish to make something in the code stand out. Later,
you may find that the modification is really sticking out like a sore thumb,
asking for someone to soothe it with a better solution. You will learn to rec-
ognize these trouble spots in your code (“code smells,” as Martin Fowler
calls them.)9
8
A method responsible for creating and returning objects. Another, perhaps more
concise term, is “creation method.”
9
[Wiki2004].
CREATING DATES WITH CALENDAR 95
You will also learn that it is cheaper to fix code problems now rather than
wait until the code is too intertwined with the rest of the system. It doesn’t
take long!
import java.util.*;
Comments
One part of the getEndDate method that could use clarification is the calculation
for the number of days to add to the session start date.
int numberOfDays = 16 * 7 - 3;
To another developer that has to maintain this method, the mathematical ex-
pression is not immediately obvious. While the maintainer can probably fig-
ure it out in a few minutes, it’s far less expensive for you as the original
developer to explain what you were thinking.
Java allows you to add free-form explanatory text within the source file in
the form of comments. The compiler ignores and discards comments encoun-
tered when it reads the source file. It is up to you to decide where and when
comments are appropriate.
Comments
You can add a single-line comment to the numberOfDays calculation. A single-
line comment begins with two forward slashes (//) and continues to the end
of the current source line. Anything from the first slash to the end of the line
is ignored by the compiler.
int numberOfDays = 16 * 7 - 3; // weeks * days per week - 3 days
Hmmm. Well, it’s more expressive, but I’m not sure that daysFromFridayToMonday
expresses exactly what’s going on. This should demonstrate that there’s not
always a perfect solution. Refactoring is not an exact science. That shouldn’t
stop you from trying, however. Most changes do improve the code, and
JAVADOC COMMENTS 97
someone (maybe you) can always come along after you and figure out an
even better way. For now, it’s your call.
Java supplies another form of comment known as a multiline comment. A
multiline comment starts with the two characters /* and ends with the two
characters */. Everything from the beginning slash through to the ending
slash is ignored by the compiler.
Note that while multiline comments can nest single line comments, multi-
line comments cannot nest other multiline comments.
As an example, the Java compiler allows the following:
int a = 1;
/* int b = 2;
// int c = 3;
*/
You should prefer single-line comments for the few places that you need to
annotate the code. The multiline comment form can then be used for rapidly
“commenting out” (turning off the code so that it is not read by the com-
piler) large blocks of code.
Javadoc Comments
Another use for multiline comments is to provide formatted code documenta-
tion that can later be used for automated generation of nicely formatted API
documentation. These comments are known as javadoc comments, since
there is a javadoc tool that will read your source files, look for javadoc com-
ments, and extract them along with other necessary information in order to
build documentation web pages. Sun’s documentation for the Java APIs
themselves was produced using javadoc.
A javadoc comment is a multiline comment. The distinction is that a
javadoc comment starts with /** instead of /*. To the javac compiler, there is
no difference between the two, since both start with /* and end with */. The
javadoc tool understands the difference, however.
98 JAVA BASICS
Javadoc comments appear directly before the Java element they are docu-
menting. Javadoc comments can appear before fields, but most typically they
are used to document classes and methods. There are rules for how a javadoc
comment must be formatted in order to be parsed correctly by the javadoc
compiler.
The primary reason for producing javadoc web pages is to document your
code for external consumption by other project teams or for public distribu-
tion. While you can code javadoc comments for every Java element (fields,
methods, classes, etc.), you should only expend the time to produce javadoc
comments for things you want to expose. Javadoc comments are intended for
telling a client developer how to work with a class.
In a team doing test-driven development, Javadoc comments are of lesser
value. If done properly, the tests you produce using test-driven development
provide far better documentation on the capabilities of a class. Practices such
as pair programming and collective code ownership, where developers work
javadoc
Comments on all parts of the system, also minimize the need for producing javadoc
comments.
If you code concise, well-named methods, with well-named parameters,
the amount of additional text that you should have to write in a Javadoc
comment should be minimal. In the absence of text, the Javadoc compiler
does a fine job of extracting and presenting the names you chose.
As a brief exercise, provide a Javadoc comment for the CourseSession
class—the single-argument constructor—and for one of the methods within
the class.
Here are the Javadoc comments I came up with:
package studentinfo;
import java.util.*;
/**
* Provides a representation of a single-semester
* session of a specific university course.
* @author Administrator
*/
class CourseSession {
private ArrayList<Student> students = new ArrayList<Student>();
private Date startDate;
CourseSession() {
}
/**
* Constructs a CourseSession starting on a specific date
*
* @param startDate the date on which the CourseSession begins
JAVADOC COMMENTS 99
*/
CourseSession(Date startDate) {
this.startDate = startDate;
}
/**
* @return Date the last date of the course session
*/
Date getEndDate() {
...
}
The javadoc program will produce a number of .html files and a stylesheet
(.css) file. Open the file index.html in a web browser and take a few minutes to
see what this simple command has produced (Figure 2.5). Fairly impressive,
no?
Well, yes and no. I’m embarrassed by the comments I had you put in for
the methods. They really added nothing that the code didn’t already state.
The @param keyword simply restates information that can be gleaned from the
parameter type and name. The @return keyword restates information that can
be derived from the method name and return type. If you find a need for
@return and @param keywords, try to rename the parameters and the method to
eliminate this need.
10
Instead of navigating to the empty directory, you can also redirect the output from
the javadoc command using its -d switch.
100 JAVA BASICS
Exercises
Remove the comments for the constructor and the methods completely,
but leave the comment for the class—it does provide a bit more value to the
reader. Then rerun the javadoc command, bring up the web pages again, and
see if you feel any useful information was lost. Your opinion may differ.
Exercises
1. Add a test to TestPawn that creates a pawn without a color. Why does
this generate a compile error? (Hint: Think about default construc-
tors.) Fix the compiler error by adding a second constructor that con-
structs a white pawn by default.
2. Make the constants for the two colors static and move them onto the
Pawn class.
3. Pawns aren’t very useful without a board. Use a test to define a Board
class. Assert that the board starts with zero pieces on it. Follow the
TDD sequence: Write the smallest test possible. Prove failure with a
red bar or a compile error. Incrementally add small bits of code to ob-
tain a clean compile or green bar.
4. Develop code to allow pawns to be added to the board. In a test, add
a black and white pawn to the board. Each time you add a pawn, as-
EXERCISES 101
sert that the piece count is correct. Also, each time you add a pawn,
obtain a list of pieces from the board and ensure that it contains the
expected pawn objects.
5. Write javadoc for each of the production classes and methods you
have created so far. Be careful in doing so: Do not duplicate informa-
tion that your methods already impart! The javadoc should be supple-
mentary information.
6. Move the four tests and classes you have created into a package of
their own. Name this package chess. Resolve compilation failures and
get to a green bar again. Also, replace fully qualified class names for
List and ArrayList by using an import statement.
7. Move TestPawn and Pawn into a package named pieces and resolve
any problems discovered along the way.
8. Ensure that nothing other than a Pawn can be added to the board. Try
Exercises
adding a new Integer("7") to the list of pawns and see the resulting com-
piler error.
9. Create a test suite that runs each test class.
10. Look through the code you have written so far. Ensure there is no du-
plication anywhere in the code. Remember that the test code is code
as well. Use the setUp method if appropriate.
This page intentionally left blank
Lesson 3
Characters
Java includes a char type that represents letters, digits, punctuation marks,
diacriticals, and other special characters. Java bases its character set on a
standard known as Unicode 4.0 for representation of its characters. The
103
104 STRINGS AND PACKAGES
Language Tests
While I present most of the code in Agile Java as part of the ongoing student ex-
Characters and
ample, I show some Java syntactical details and variations as brief code snippets
Strings
and assertions. The single-line assertions in this section on characters provide an
example. I refer to these as language tests—you write them to learn the language.
You might keep them to reinforce your understanding of the language for later
use.
You can code these tests wherever you like. I typically code them as separate
test methods in the current test class, then delete them once I have an understand-
ing of the element I was working on.
You may choose to create a separate class to contain these “scratch” tests. Ulti-
mately, you might even create a scratch package, suite, and/or project of such tests.
You might get some reuse out of storing these tests: Some of the language tests
you build may end up being the basis for utility methods that encapsulate and sim-
plify use of a language feature.
Not all characters can be directly entered via the keyboard. You can repre-
sent Unicode characters using the Unicode escape sequence, \u or \U, followed
by a 4-digit hex number.
assertEquals('\u0041', capitalA);
CHARACTERS AND STRINGS 105
assertEquals('\101', capitalA);
The highest possible character literal that you may represent as an octal se-
quence is '\377', which is equivalent to 255.
Most older languages (for example, C) treat characters as single bytes. The
most well-known standard for representing characters in a single-byte char-
acter set (SBCS), the American Standard Code for Information Interchange
(ASCII), is defined by ANSI X3.4.1 The first 128 characters of Unicode map
directly to their ASCII correspondents.
Special Characters
Java defines several special characters that you can use for things such as out-
put formatting. Java represents the special characters with an escape se-
quence that consists of the backslash character (\) followed by a mnemonic.
The table below summarizes the char literals that represent these special
Characters and
characters. Strings
Since the tic character and the backslash character have special meanings
with respect to char literals, you must represent them with an escape sequence.
You may also escape (i.e., prefix with the escape character \) the double
quote character, but you are not required to do so.
1
In fact, ASCII is only a true standard for seven of the single byte’s eight bits. Charac-
ters from 0 through 127 are consistently represented, but there are several competing
standards for characters 128 through 255.
106 STRINGS AND PACKAGES
Strings
A String object represents a sequence of char values of fixed length. The String
class in Java is probably the most frequently used class in any Java applica-
tion. Even in a small application, thousands of String objects will be created
over and over.
The String class supplies dozens of methods. It has special performance
characteristics that make it different from most other classes in the system.
Finally, even though String is a class like any other class in the system, the
Java language provides special syntactical support for working with String
objects.
You can construct Strings in a number of ways. Any time you create a new
String literal, the Java VM constructs a String object behind the scenes. Here
are two ways to construct a String object and assign it to a reference variable:
String a = "abc";
String b = new String("abc"); // DON'T DO THIS
Strings Avoid the second technique.2 It creates two String objects, which can de-
grade performance: First, the VM creates the literal String object "abc". Sec-
ond, the VM constructs a new String object, passing the literal "abc" to its
constructor. Equally as important, it is an unnecessary construct that makes
your code more difficult to read.
Since strings are sequences of characters, they can embed special charac-
ters. The string literal in the following line of code contains a tab character
followed by a line feed character.
String z = "\t\n";
String Concatenation
You may concatenate a string to another string to produce a third string.
assertEquals("abcd", "ab".concat("cd"));
String concatenation is such a frequent operation in Java that you can use the
plus sign (+) as a shortcut for concatenating strings. In fact, most Java con-
catenations are written this way:
assertEquals("abcdef", "abc" + "def");
2
[Bloch2001].
STRINGBUILDER 107
In the previous lesson, you used + for addition of integers. Java also allows
you to use the plus sign, known as an operator, to concatenate strings. Since
the + operator has different meanings, depending on what you use it with, it
is known as an overloaded operator.
String Immutability
As you browse through the Java API documentation for String, you will no-
tice that there are no methods that change a string. You cannot alter the
length of a string, nor can you alter any of the characters contained within a
string. String objects are thus immutable. If you want to perform any manip-
ulations on a string, you must create a new string. For example, when you
concatenate two strings using +, the Java VM alters neither string. Instead, it
creates a new string object. StringBuilder
Sun designed String to be immutable to allow it to act in a very optimized
manner. This optimization is crucial due to the very heavy use of Strings in
most applications.
StringBuilder
Often you will need to be able to construct strings dynamically. The class
java.lang.StringBuilder provides this capability. A newly created StringBuilder
represents an empty sequence, or collection, of characters. You can add to this
collection by sending the append message to the StringBuilder object.
Just as Java overloads the + operator to support both adding int values and
concatenating strings, the StringBuilder class overloads the append method to
take arguments of any base type. You can pass a character, a String, an int, or
other types as arguments to an append message. Refer to the Java API docu-
mentation for the list of overloaded methods.
When you finish appending to the StringBuilder, you obtain a concate-
nated String object from the StringBuilder by sending it the toString message.
Users of the student information system need to produce a report
showing the roster of a course session. For now, a simple text report
with just a list of the student names in any order will suffice.
108 STRINGS AND PACKAGES
Code the following test in CourseSessionTest. The assertion shows that the
report requires a simple header and a footer showing the count of students.
public void testRosterReport() {
session.enroll(new Student("A"));
session.enroll(new Student("B"));
buffer.append(ROSTER_REPORT_HEADER);
StringBuilder
Student student = students.get(0);
buffer.append(student.getName());
buffer.append('\n');
student = students.get(1);
buffer.append(student.getName());
buffer.append('\n');
return buffer.toString();
}
For each of the two students, you pass a String (the student’s name) to
append,then you pass a char (line feed) to append. You also append header and
footer information to the StringBuilder object stored in buffer. The name buffer
implies that the StringBuilder holds on to a collection of characters that will
be used later. The line that constructs the footer demonstrates how you can
pass a concatenated string as the parameter to the append method.
You defined the getRosterReport method in the CourseSession class. Code
within a class can refer directly to static variables. So instead of:
CourseSession.ROSTER_REPORT_HEADER
When you learn more about the static keyword in Lesson 4, you will be told
to refer to static variables and static methods only by scoping them with the
class name (as in CourseSession.ROSTER_REPORT_HEADER), even from within the class they
are defined. Doing otherwise obscures the fact that you are using static ele-
ments, which can lead to troublesome defects. In the case of class constants,
however, the naming convention (UPPERCASE_WITH_UNDERSCORES) makes it explicitly clear
that you are referring to a static element. The second, unscoped, form is thus ac-
ceptable (although some shops may prohibit it) and is an exception to the rule.
If you look at older Java code, you will see use of the class java.lang.String-
Buffer. You interact with a StringBuffer object the same as with a String-
Builder object. The distinction between the two is that the StringBuilder class
has better performance characteristics. It does not need to support multi-
threaded applications, where two pieces of code could be working with a
StringBuffer simultaneously. See Lesson 13 for a discussion of multithreading.
System Properties
System
Both the method getRosterReport and its test contain the use of '\n' to represent a Properties
line feed in many places. Not only is this duplication, it is not portable—dif-
ferent platforms use different special character sequences for advancing to a
new line on output. The solution to this problem can be found in the class
java.lang.System. As usual, refer to the J2SE API documentation for a more
detailed understanding of the System class.
The System class contains a method named getProperty that takes a system
property key (a String) as a parameter and returns the system property value
associated with the key. The Java VM sets several system properties upon
startup. Many of these properties return information about the VM and exe-
cution environment. The API documentation method detail for getProperties
shows the list of available properties.
One of the properties is line.separator. According to the Java API documen-
tation, the value of this property under Unix is '\n'. However, under Win-
dows, the value of the property is '\r\n'. You will use the line.separator system
property in your code to compensate for the differences between platforms.
The following changes to the test and to CourseSession demonstrate use of
the System method getProperty.
Test code:
public void testRosterReport()
{
Student studentA = new Student("A");
110 STRINGS AND PACKAGES
Production code:
class CourseSession {
static final String NEWLINE =
System.getProperty("line.separator");
static final String ROSTER_REPORT_HEADER =
"Student" + NEWLINE +
"———-" + NEWLINE;
static final String ROSTER_REPORT_FOOTER =
NEWLINE + "# students = ";
Looping ...
through All String getRosterReport() {
Students StringBuilder buffer = new StringBuilder();
buffer.append(ROSTER_REPORT_HEADER);
student = students.get(1);
buffer.append(student.getName());
buffer.append(NEWLINE);
return buffer.toString();
}
}
The second form allows you to define the body as a single statement only,
and thus requires no braces:
Looping
for (Student student: students)
through All
// ... single statements here;
Students
In Lesson 7, you will learn about another kind of for loop that allows you to
loop a certain number of times instead of looping through every element in a
collection.
The Java VM executes the body of the for loop once for each student in
the collection students.
String getRosterReport() {
StringBuilder buffer = new StringBuilder();
buffer.append(ROSTER_REPORT_HEADER);
return buffer.toString();
}
A reading of the above for-each loop in English-like prose: Assign each ob-
ject in the collection students to a reference of the type Student named student
and execute the body of the for loop with this context.
3
Also known as an enhanced for loop.
112 STRINGS AND PACKAGES
Single-Responsibility Principle
New reports are continually needed in the student information sys-
tem. You have been told that you must now produce three additional
reports. And you can surmise that new reports will continue to be re-
quested. You foresee the need to change the CourseSession class constantly as
the reports are added.
One of the most basic design principles in object-oriented programming is
that a class should do one thing and do it well. By virtue of doing this one
thing, the class should have only one reason to change. This is known as the
Single-Responsibility Principle.4
package studentinfo;
import junit.framework.TestCase;
import java.util.*;
session.enroll(new Student("A"));
session.enroll(new Student("B"));
4
[Martin2003].
SINGLE-RESPONSIBILITY PRINCIPLE 113
RosterReporter.ROSTER_REPORT_FOOTER + "2" +
RosterReporter.NEWLINE, rosterReport);
}
package studentinfo;
import junit.framework.TestSuite;
Much of the work of getting the test to pass involves moving the code over
from CourseSession. Do this incrementally—don’t make any changes to
CourseSession or CourseSessionTest until everything is working in RosterRe-
porter.
114 STRINGS AND PACKAGES
package studentinfo;
import java.util.*;
class RosterReporter {
static final String NEWLINE =
System.getProperty("line.separator");
static final String ROSTER_REPORT_HEADER =
"Student" + NEWLINE +
"———-" + NEWLINE;
static final String ROSTER_REPORT_FOOTER =
NEWLINE + "# students = ";
RosterReporter(CourseSession session) {
this.session = session;
}
String getReport() {
StringBuilder buffer = new StringBuilder();
buffer.append(ROSTER_REPORT_HEADER);
buffer.append(
ROSTER_REPORT_FOOTER + session.getAllStudents().size() +
NEWLINE);
return buffer.toString();
}
}
The bold code in the example above shows the significant differences be-
tween RosterReporter and the corresponding code in CourseSession.
To arrive at the code above, first paste the body of getReport from Course-
Session directly into the corresponding method in RosterReporter. Then modify
the pasted code to request the collection of students from Course-
Session by sending the getAllStudents message instead of accessing it directly (since
the method no longer executes in CourseSession). Since you removed getAll
Students in the previous lesson, you’ll just have to add it back to CourseSession.
class CourseSession {
...
ArrayList<Student> getAllStudents() {
return students;
}
...
}
REFACTORING 115
CourseSession Student
RosterReporter
Refactoring
Both CourseSessionTest and RosterReporterTest require the createDate utility
method. The code in createDate has nothing to do with course sessions or ros-
ter reports; it deals solely with constructing date objects. Including minor
utility methods in classes is a mild violation of the Single-Responsibility
Principle. You can tolerate small doses of duplication, but in doing so you
quickly open the door to excessive, costly duplication in your system. In a
larger system, there might be half a dozen methods that construct dates, all
with pretty much the same code.
Here the duplication is obvious, since you directly created (and hopefully
noted) it. An alternate approach is to not even let the duplication occur: As
soon as you recognize that you might be introducing duplicate code, do the
necessary refactoring first to stave off the potential duplication.
116 STRINGS AND PACKAGES
You will create a new test class and production class. You must update
AllTests to reference the new test class. The code for all three follows.
// DateUtilTest.java
package studentinfo;
import java.util.*;
import junit.framework.*;
// DateUtil.java
package studentinfo;
class DateUtil {
Date createDate(int year, int month, int date) {
GregorianCalendar calendar = new GregorianCalendar();
calendar.clear();
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month - 1);
calendar.set(Calendar.DAY_OF_MONTH, date);
return calendar.getTime();
}
}
// AllTests.java
package studentinfo;
import junit.framework.TestSuite;
Previously, no tests existed for the createDate method, since it was only a
utility for use in test classes themselves. When extracting code from one class
REFACTORING 117
to a new class, you should always move along any tests that exist into the
corresponding new test class. When tests do not exist, you should expend the
time to create them. This will maintain the sustainability of your system.
Now that you have created and tested the DateUtil class, you want to up-
date your code to refer to it. At the same time, you want to remove the create-
Date method from both CourseSessionTest and RosterReporterTest. One solid
approach is to remove the createDate method from both places and recompile.
The compiler will tell you precisely which lines of code refer to the nonexis-
tent createDate method.
// CourseSessionTest
package studentinfo;
import junit.framework.TestCase;
import java.util.*;
// RosterReporterTest.java
package studentinfo;
import junit.framework.TestCase;
In order to use the createDate utility method, you must construct a DateUtil
object each time. In the case of CourseSession test, you construct a DateUtil
object twice—a prime candidate for refactoring. You could create an instance
118 STRINGS AND PACKAGES
System.out
The getReport method returns a String that contains a report of all the students
enrolled in a course session. In the production student information system,
the String isn’t going to be of much use to anyone unless you print it out or
display it somewhere. Java provides output facilities to allow you to redirect
information to the console, to files, and to other destinations. You will learn
about these output facilities in depth in Lesson 11.
In this exercise you will modify the test so that the report displays on the
console. It isn’t yet a requirement, but sometimes you need to be able to dis-
play things for various reasons. The next section goes into some of these
reasons.
System.out In the Setup section of this book, you coded and ran a “Hello World” ap-
plication that printed text to your console. The line of code to print the text
on the console was:
System.out.println("hello world");
Look at the J2SE API documentation for the class named System, located
in the package java.lang. You will see that out is a static variable, of the type
PrintStream, that represents the standard output stream, also known as std-
out or simply “the console.” You can directly access this console object using
the following static variable reference:
System.out
Once you have this console object, you may send it a number of messages,
including the message println. The println method takes a String (among other
things) and writes it to the underlying output stream.
Add a line to RosterReporterTest that displays the report on the console
by using System.out:
package studentinfo;
import junit.framework.TestCase;
session.enroll(new Student("A"));
session.enroll(new Student("B"));
Rerun your tests. You should see the actual report displayed onscreen. If
you are running in an IDE, you may need to use System.err (the standard error
output stream, also known as syserr), instead of System.out, in order to view
the results.5
You’ll note that I placed the additional line of code all the way to the left
margin. I use this convention to remind me that the code is intended for tem- Using
porary use. It makes such statements easy to locate and remove. System.out
Revert these changes and rerun all tests once you have finished viewing
the output.
Using System.out
The most frequent use of System.out is to post messages to the console in an ef-
fort to locate defects in a program. You insert System.out.println statements to
display useful information at judicious points in your code. When you exe-
cute the application, the output from these trace statements can help you un-
derstand the flow of messages and data through the objects interacting in the
system.
Debuggers are far more sophisticated tools that accomplish the same goal
and much more, but simple trace statements can occasionally be a more
rapid and effective solution. Also, in some environments it is not feasible to
use a debugger.
Regardless, you should find minimal need to debug your code, or even in-
sert trace statements into it, if you do TDD properly. If you do the small
5
The results might appear in a window named “console.”
120 STRINGS AND PACKAGES
steps that TDD prescribes, you will introduce very small amounts of code
into your application before finding out you have a problem. Instead, the
better solution is to discard the small amount of newly introduced code and
start again, using even smaller verified steps.
Refactoring
If you haven’t already done so, remove the testReport method from CourseSes-
sionTest and remove the corresponding production code from CourseSession.
Refactoring The writeReport method is still short, but conceptually it is doing three
things. To make understanding even more immediate, you can decompose
the code in writeReport into three smaller methods, one each to construct the
header, body, and footer of the report:
String getReport() {
StringBuilder buffer = new StringBuilder();
writeHeader(buffer);
writeBody(buffer);
writeFooter(buffer);
return buffer.toString();
}
Package Structure
You use packages to arbitrarily group your classes. This grouping of classes,
known as the package structure, will change over time as your needs change.
Initially, your concern will be ease of development. As the number of classes
grows, you will want to create additional packages for manageability rea-
sons. Once you deploy the application, your needs may change: You may
want to organize the packages to increase the potential for reuse or perhaps
to minimize maintenance impact to consumers of the package.
So far, your classes have all ended up in one package, studentinfo. A
typical way to start organizing packages is to separate the user interface—
the part of the application that the end user interacts with—from the
underlying classes that represent business objects and infrastructural ob-
jects. The RosterReporter class in the previous example could be construed
as part of the user interface, as it produces output that the end user will
see.
Your next task will be to first move the studentinfo package down a level so
that it is in a package named sis.reportinfo. You will then separate the Roster- Package
Reporter and RosterReporterTest classes into their own package named Structure
report.
First create a new subdirectory named sis (for “Student Information Sys-
tem”) at the same directory level as studentinfo. Beneath this directory, create
a new subdirectory named report. Move the studentinfo subdirectory into the
sis subdirectory. Move the RosterReporter and RosterReporterTest classes
into the report subdirectory. Your directory structure should look something
like:
source
|—-sis
|—-studentinfo
|—-report
Next, you will change the package statements of all your classes. For the
packages in the report subdirectory, use this package statement:
package sis.report;
For the packages in the studentinfo subdirectory, use this package statement:
package sis.studentinfo;
As you did in Lesson 2, remove all the class files (*.class), then recompile all
your code. You will receive several errors. The problem is that the Roster-
Reporter and RosterReporterTest classes are now in a separate package from
122 STRINGS AND PACKAGES
the CourseSession and Student classes. They no longer have appropriate ac-
cess to the classes in the other package.
Access Modifiers
You have already used the keyword public for JUnit classes and methods with-
out an understanding of the full meaning of the keyword, other than that
JUnit requires that test classes and methods be declared public. You also
learned that instance variables can be declared private so that objects of other
classes cannot access them.
The public and private keywords are known as access modifiers. You use ac-
cess modifiers to control access to Java elements, including fields, methods,
and classes. The access modifiers that are appropriate for a class are different
than those that are appropriate for methods and fields.
By declaring a class as public, you allow classes in other packages to be able
to import and refer directly to the class. The JUnit framework classes are lo-
Access cated in various packages whose name starts with junit. In order for these
Modifiers JUnit classes to be able to instantiate your test classes, you must declare them
as public.
Neither the CourseSession nor the Student class you built specified an ac-
cess modifier. In the absence of an access modifier, a class has an access level
of package, also known as default access. You can refer to a class with pack-
age-level access from other classes within the same package; however, classes
in a different package cannot refer to the class.
For “safer” programming, the preferred tactic is to start at the most re-
strictive level and then open up access as needed. Exposing your classes too
much can mean that clients can become unnecessarily dependent on the de-
tails of how you’ve put the system together. If you change the details, the
clients could break. Also, you open your code up to being corrupted by pro-
viding too much access.
Protect your code as much as possible. Relax access modifiers
only when necessary.
package sis.report;
import junit.framework.*;
import sis.studentinfo.*;
package sis.studentinfo;
import junit.framework.TestSuite;
You will soon create a new AllTests for the reports package. Be careful
when commenting out code—it’s easy to forget why the code is commented
out.
After recompiling, you will receive lots of errors for each message sent
from the code in the reports package to Student and CourseSession objects.
Like classes, the default access level for constructors (and methods) is pack-
age. Just as classes need to be public in order to be accessed from outside the
package, methods and constructors also must be declared as public. Do so ju-
diciously—you should never make blanket declarations of every method as
public.
124 STRINGS AND PACKAGES
As a matter of style and organization, you may also want to move public
methods so they appear before the non-public methods in the source file. The
idea is that a client developer interested in your class will find the public meth-
ods—the methods they should be most interested in—first. With IDEs, this
organization is not as necessary, as most IDEs provide a better way to orga-
nize and navigate through source for a class.
When finished, the production classes in studentinfo should look something
like the following.
Student.java:
package studentinfo;
CourseSession.java:
package studentinfo;
import java.util.*;
/**
* This class provides a representation of a single-semester
* session of a specific university course.
* @author Administrator
*/
public class CourseSession {
private String department;
private String number;
private ArrayList<Student> students = new ArrayList<Student>();
private Date startDate;
/**
* Constructs a CourseSession starting on a specific date
* @param startDate the date on which the CourseSession begins
*/
public CourseSession(
String department, String number, Date startDate) {
this.department = department;
this.number = number;
this.startDate = startDate;
}
ACCESS MODIFIERS 125
String getDepartment() {
return department;
}
String getNumber() {
return number;
}
int getNumberOfStudents() {
return students.size();
}
Date getStartDate() {
return startDate;
}
Access
public ArrayList<Student> getAllStudents() {
Modifiers
return students;
}
/**
* @return Date the last date of the course session
*/
Date getEndDate() {
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(startDate);
final int sessionLength = 16;
final int daysInWeek = 7;
final int daysFromFridayToMonday = 3;
int numberOfDays =
sessionLength * daysInWeek - daysFromFridayToMonday;
calendar.add(Calendar.DAY_OF_YEAR, numberOfDays);
return calendar.getTime();
}
}
DateUtil.java:
package studentinfo;
import java.util.*;
calendar.clear();
calendar.set(Calendar.YEAR, year - 1900);
calendar.set(Calendar.MONTH, month - 1);
calendar.set(Calendar.DAY_OF_MONTH, date);
return calendar.getTime();
}
}
At this point, everything should compile. Your tests should also run, but
don’t forget that you commented out RosterReporterTest. It’s time to add it
back in.
ACCESS MODIFIERS 127
Create a new class named AllTests in the sis.report package. Generally you
will want a test suite in each package to ensure that all classes in the package
are tested.6
package sis.report;
import junit.framework.TestSuite;
You can now remove the commented-out line from the class student-
info.AllTests.
Create a class named AllTests in the sis package by placing its source file
in the sis directory. This class will produce the combined test suite that en-
sures all classes in the application are tested. Access
Modifiers
package sis;
import junit.framework.TestSuite;
Instead of sending the message addTestSuite to the suite, you send the mes-
sage addTest. As a parameter, you pass along the results of sending the message
suite to the appropriate AllTests class. Sending a message to a class instead of
to an object will result in a static method being called. I will discuss static
methods in the next lesson.
You will want to pass sis.AllTests to JUnit in order to run your entire test
suite.
6
There are other ways of managing test suites; your IDE may provide some assistance
here. Also, refer to Lesson 12 for a dynamic way of gathering tests.
128 STRINGS AND PACKAGES
Using Ant
From here on out, I will be using an Ant script to do my compilations,
now that I have more than two directories to compile. Ant is a platform-
independent tool that allows you to create specifications of how your project
should be built and deployed.
If you are using an IDE, you should be able to get it to build your entire
codebase easily. Under Eclipse, for example, all of your source code is com-
piled automatically each time you save changes to Java source.
Regardless of whether or not you are using an IDE, you may want to use
Ant in order to obtain IDE and platform independence. Other alternatives
are to build a shell script or batch file as I demonstrated in Lesson 1. You can
also use one of a number of available make tools. A make tool is a build tool
very similar to Ant, but most make tools are very tightly bound to a specific
operating system. Few make tools provide the ease of building Java applica-
tions that Ant does. Ant is the most effective way of doing builds in Java.
I highly recommend that you learn how to use Ant. Your IDE may suffice
Using Ant for your own personal needs, but it might not be sufficient in a team environ-
ment. If you work in a team environment, you will want a standardized way
of building and deploying your application. Most development shops have
standardized on Ant as a way of ensuring the system is built and deployed
consistently and correctly.
See the sidebar “Getting Started with Ant” for a brief overview of using
Ant.
<path id="classpath">
<pathelement location="${junitJar}" />
<pathelement location="${build.dir}" />
</path>
<target name="init">
<mkdir dir="${build.dir}" />
</target>
<target name="clean">
<delete dir="${build.dir}" />
</target>
The above line defines a target named rebuildAll. When you execute this target
(keep reading), Ant first ensures that the targets named clean and build have been
executed.
130 STRINGS AND PACKAGES
A target contains a list of commands, or tasks, to execute. The Ant software in-
stallation provides a manual describing a large number of tasks that will suffice for
most of your needs. If you can’t find an appropriate task, you can programmati-
cally create your own.
The clean target contains the single task named delete. In this example, the delete
task tells Ant to delete a file system directory with the name provided in quotes.
<target name="clean">
<delete dir="${build.dir}" />
</target>
Ant allows you to define properties that provide a construct similar to con-
stants in Java. When the delete task executes, Ant will replace ${build.dir} with the
value of the property named build.dir. The property named build.dir is declared in
the agileJava Ant script as:
<property name="build.dir" value="${basedir}\classes" />
The default attribute in the project element indicates the target to execute if none is
specified. In this example, the junitgui target is the default and gets run if you exe-
cute ant with no arguments:
ant
This will show main targets—those targets that specify a description attribute.
Using some built-in smarts, Ant executes tasks only when necessary. For exam-
ple, if you execute the junitgui target, Ant will only run javac compiles against
source that has not changed since the last time you executed junitgui. It uses the
timestamps of the class files to make this determination.
To summarize the agileJava project, there are three main targets: build, junitgui,
and rebuildAll. There are two subtargets, init and clean.
The build target depends on the init target, which ensures that the build output
directory (./classes) exists. The build target compiles all sources in the source direc-
tory (./source) to the build output directory, using Ant’s built-in javac task. The
javac task specifies a number of attributes, including the attribute classpath, speci-
fied as a nested element of the javac task. The classpath attribute references a path
EXERCISES 131
element by the name classpath; this path element includes the JUnit jar file and the
classes directory.
The junitgui target depends on the build target. If the build target succeeds, the
junitgui target executes the JUnit GUI using the Java VM, passing in AllTests as the
parameter.7
The rebuildAll target depends on execution of the clean target, which removes
the build output directory, and on the build target.
Refer to the Ant manual for more detailed information. There are also several
books available on Ant. One very comprehensive book is Java Development with
Ant.8
Exercises
1. Create a CharacterTest class. Don’t forget to add it to the AllSuites
class. Observe the zero tests failure. Then add a test named testWhite-
space. This test should demonstrate that the new line character, the tab
character, and the space character all return true for Character.isWhite-
space. Express that other characters return false. Can you find another Exercises
character that returns true?
2. Java has certain naming restrictions on identifiers—the names you
give to methods, classes, variables, and other entities. For example,
you cannot use the caret (^) in an identifier name. The Character class
contains methods that designate whether or not a character can be
used in an identifier. Consult your API documentation to understand
these methods. Then add tests to the CharacterTest class to discover
some of the rules regarding Java identifiers.
3. Assert that a black pawn’s printable representation is the uppercase
character 'P', and a white pawn’s is the lowercase character 'p'. For
the time being, you can accomplish this by adding a second parameter
to the Pawn constructor. However, note that this creates a redundancy
in representation. You’ll improve upon the solution later.
4. (This exercise and Exercise 5 are closely related. You may suspend
refactoring until you have completed Exercise 5.) When a client
creates a Board object, they should be able to assume that the board is
already initialized, with pieces in place. You will need to modify the
7
There is also an optional Ant task named junit that executes a text-based JUnit.
8
[Hatcher2002].
132 STRINGS AND PACKAGES
Class Methods
Objects are a combination of behavior (implemented in Java in terms of
methods) and attributes (implemented in Java in terms of fields). Attributes
for an object stick around as long as the object sticks around. At any given
point in time, an object has a certain state, which is the combined snapshot
of all its instance variables. For this reason, instance variables are sometimes
called state variables.
Action methods in the object may operate on and change attributes of the
object. In other words, action methods can alter the object state. Query
methods return pieces of the object state.
133
134 CLASS METHODS AND FIELDS
Occasionally you will find the need for a method that can take parame-
ters, operate on only those parameters, and return a value. The method has
no need to operate on object state. This is known as a utility method. Utility
methods in other languages are sometimes called functions. They are global:
any client code can access them.
Sometimes, having to create an object in order to use a utility method
makes little sense. For example, the DateUtil method createDate you coded in
Lesson 3 is a simple function that takes month, day, and year integer argu-
ments and returns a Date object. The createDate method changes no other data.
Removing the need to construct DateUtil objects will also simplify your code
a bit. Finally, since createDate is the only method in DateUtil, there is no other
need to construct instances of it.
For these reasons, createDate is a good candidate for being a class method.
In this exercise, you will refactor createDate to be a class method in this exer-
cise. Start by changing the test to make a class method call:
package sis.studentinfo;
import java.util.*;
import junit.framework.*;
Class Methods
public class DateUtilTest extends TestCase {
public void testCreateDate() {
Date date = DateUtil.createDate(2000, 1, 1);
Calendar calendar = new GregorianCalendar();
calendar.setTime(date);
assertEquals(2000, calendar.get(Calendar.YEAR));
assertEquals(Calendar.JANUARY, calendar.get(Calendar.MONTH));
assertEquals(1, calendar.get(Calendar.DAY_OF_MONTH));
}
}
You no longer create an instance of DateUtil with the new operator. Instead,
to call a class method, you specify the class on which the class method is de-
fined (DateUtil), followed by the dot operator (.), followed by the method
name and any arguments (createDate(2000, 1, 1)).
Changes to the DateUtil class itself are similarly minor:
package sis.studentinfo;
import java.util.*;
You declare a class method just like a “regular,” or instance, method, except
that you prefix its declaration with the keyword static.
In addition to making the createDate method static, it’s a good idea to make
the constructor of DateUtil private. By declaring the constructor as private,
only code in the DateUtil class can construct new DateUtil instances. No
other code will be able to do so. While it wouldn’t be harmful to allow cre-
ation of DateUtil objects, keeping clients from doing something nonsensical
and useless is a good idea.
Adding the private constructor will also make it simpler for you to pin-
point the nonstatic references to createDate. When you compile your code,
methods that create a new DateUtil object will generate compilation errors.
For example, the setUp method in CourseSessionTest will fail compilation:
public void setUp() {
startDate = new DateUtil().createDate(2003, 1, 6);
session = new CourseSession("ENGL", "101", startDate); Class Methods
}
Fix the remainder of the failing compilation problems and rerun your
tests. You now have a general-purpose utility that may find frequent use in
your system.1
The class java.lang.Math in the J2SE 5.0 class library supplies many many
mathematical functions. For example, Math.sin returns the sine of a double and
Math.toRadians converts a double value from degrees to radians. The Math class
1
The utility is not the best-performing one. It is not necessary to create a Gregorian-
Calendar object with each call to createDate. For sporadic use, this is probably just
fine. For heavy use—say, creating 10,000 dates upon reading an input file—you’ll
want to consider caching the calendar object using a class variable (see the next sec-
tion).
136 CLASS METHODS AND FIELDS
abs
acos
asin
atan
...
also provides two standard mathematical constants, Math.PI and Math.E. Since
each of the methods in java.lang.Math is a utility class method, it is known
as a utility class.
In UML (Figure 4.1), you indicate a utility class using the stereotype
<<utility>>. Stereotypes in UML define semantics beyond the limitations of
what UML supplies. A utility stereotype specifies that all class behaviors and
attributes may be globally accessed.
Normally you underline class behaviors and class attributes in UML. Since
the <<utility>> stereotype declares that all methods and attributes in a class are
global, you need not underline them.
Class Methods
Class Variables
You will occasionally want to track information about all instances of a class
or perform an operation without first creating an instance of an object. As a
simplistic example, you might want to track the total number of course ses-
sions. As each CourseSession object is created, you want to bump up a
counter. The question is, where should you put this counter? You could pro-
vide an instance variable on CourseSession to track the count, but this is
awkward: Would all instances of CourseSession have to track the count?
How would one CourseSession instance know when others were created so
that it could update the count?
You could provide another class, CourseSessionCounter, whose sole re-
sponsibility is to track the CourseSession objects created. But a new class
seems like overkill for the simple goal you are trying to accomplish.
In Java, you can use class variables, as opposed to instance variables, for a
solution. Client code can access a class variable without first creating an in-
stance of that class. Class variables have what is known as static scope: they
exist as long as the class exists, which is pretty much from the time the class
is first loaded until your application terminates.
You have already seen class constants in use. Class constants are class
variables that you have designated as final.
Class Variables
The following test code (in CourseSessionTest) counts the number of
CourseSession instances created:
public void testCount() {
CourseSession.count = 0;
createCourseSession();
assertEquals(1, CourseSession.count);
createCourseSession();
assertEquals(2, CourseSession.count);
}
public CourseSession(
String department, String number, Date startDate) {
this.department = department;
this.number = number;
this.startDate = startDate;
CourseSession.count = CourseSession.count + 1;
}
// ...
You access the class variable count similar to the way you call a class
method: First specify the class name (CourseSession), followed by the dot (.)
operator, followed by the variable name (count). The Java VM does not create
an instance of CourseSession when code accesses the class variable.
As I mentioned, class variables have a different lifetime than instance vari-
ables. Instance variables stick around for the lifetime of the object that con-
tains them. Each new CourseSession object that the Java VM creates manages
its own set of the instance variables declared in CourseSession. When the VM
creates a CourseSession object, it initializes its instance variables.
A class variable, however, comes into existence when the Java VM first
loads the containing class—when code that is currently executing first refer-
ences the class. There is one copy of the class variable in memory. The first
time the Java VM loads a class, it initializes its class variables, and that’s it. If
you need to reset a class variable to an initial state at a later time, you must
explicitly initialize it yourself.
Operating on
Class Variables As an experiment, comment out the first line in testCount (the line that reads
with Class CourseSession.count = 0). Then run the tests in JUnit. Turn off the checkbox in
Methods JUnit that says “Reload classes every run.”2 If you run the tests twice (by
clicking the Run button), they will fail, and you should see the actual count
go up with each execution of the tests. You may even see the first run of the
test fail: Other test methods in CourseSessionTest are creating CourseSession
objects, which increments the count variable.
2
This JUnit switch, when turned on, results in your test classes being physically
loaded from disk and reinitialized each time the tests are run in JUnit. If you are run-
ning in an IDE such as Eclipse, you may not have control over this JUnit feature.
OPERATING ON CLASS VARIABLES WITH CLASS METHODS 139
In addition to being able to use class methods for utility purposes, you can
use class methods to operate on static data.
The CourseSession method testCount accesses the count class variable directly.
Change the test code to ask for the count by making a class method call.
public void testCount() {
CourseSession.count = 0;
createCourseSession();
assertEquals(1, CourseSession.getCount());
createCourseSession();
assertEquals(2, CourseSession.getCount());
}
Then add a class method to CourseSession that returns the class variable
count.
Class methods can access class variables directly. You should not specify
the class name when accessing a class variable from a class method.
The Java VM creates no instances of CourseSession as a result of calling
the class method. This means that class methods on CourseSession may not
access any of the instance variables that CourseSession defines, such as depart-
ment or students.
Operating on
The test method still refers directly to the count class variable, however, Class Variables
since you need it initialized each time the test is run: with Class
Methods
public void testCount() {
CourseSession.count = 0;
...
count = 0;
}
Making the count variable private will point out (when you recompile) any
other client code that directly accesses it.
The testCount method, which documents how a client should use the Course-
Session class, is now complete and clean. But the CourseSession class itself
still accesses the class variable directly in its constructor. Instead of accessing
static data directly from a member (an instance-side constructor, field, or
method), a better approach is to create a class method that you call from the
instance side. This is a form of encapsulation that will give you greater con-
trol over what happens to the class variable.
Change the CourseSession constructor to send the incrementCount message in-
stead of accessing the class variable directly:
public CourseSession(String department, String number, Date startDate) {
this.department = department;
this.number = number;
this.startDate = startDate;
CourseSession.incrementCount();
Operating on }
Class Variables
with Class Then add a class method to CourseSession that increments the count. De-
Methods clare this method as private to prevent other classes from incrementing the
counter, which could compromise its integrity:
private static void incrementCount() {
count = count + 1;
}
Even though it will work, avoid doing this. Accessing class methods with-
out using the class name introduces unnecessary confusion in your code and
is considered bad form. Is incrementCount a class method or an instance method?
STATIC IMPORT 141
Since it’s not possible to tell from looking at the code in the CourseSession
constructor alone, the intent is not clear. The expectation that a method is an
instance method when it is in reality a class method can lead to some interest-
ing problems.
Scope a class method call with the class name when invoking the
class method from anywhere but another class method on the same
class.
Static Import
I just told you to not call class methods from the instance side unless you
supply the class name. Doing so obscures where the class method is defined.
The same applies for accessing class variables (not including class
constants).
Java permits you to muddle things even further. Including a static import
in a class allows you to use class methods or variables defined in a different
class as if they were defined locally. In other words, a static import allows
you to omit the class name when referring to static members defined in an-
other class.
There are appropriate uses for static import and inappropriate uses. I’ll
Static Import
demonstrate an inappropriate use first. Modify CourseSessionTest:
// avoid doing this
package sis.studentinfo;
import junit.framework.TestCase;
import java.util.*;
import static sis.studentinfo.DateUtil.*; // poor use of static import
If DateUtil were to contain more than one class method with the name
createDate (but with different argument lists), or if it also were to contain a
class variable named createDate, they would each be statically imported.
Statically importing methods to avoid having to provide a class name in a
few spots is lazy and introduces unnecessary confusion. Just where is createDate
defined? If you are coding a class that requires quite a few external class
method calls (perhaps a couple dozen or more), you might have an excuse to
use static import. But a better approach would be to question why you need
to make so many static calls in the first place and perhaps revisit the design
of the other class.
A similar, potentially legitimate use of static import is to simplify the use
of several related class constants that are gathered in a single place. Suppose
you’ve created several report classes. Each report class will need to append
Static Import new line characters to the output, so each report class will need a NEWLINE con-
stant such as the one currently defined in RosterReporter:
You don’t want the duplication of defining this constant in each and every
report class. You might create a new class solely for the purpose of holding
this constant. Later it might hold other constants such as the page width for
any report.
package sis.report;
Since the NEWLINE constant will be used in a lot of places in a typical report
class, you can add a static import to clean up your code a little:3
3
You can eliminate the need for the NEWLINE constant entirely in a few other ways.
You’ll learn about one such technique, using the Java Formatter class, in Lesson 8.
STATIC IMPORT 143
package sis.report;
import junit.framework.TestCase;
import sis.studentinfo.*;
import static sis.report.ReportConstant.NEWLINE;
session.enroll(new Student("A"));
session.enroll(new Student("B"));
Use static imports with prudence. They make it more difficult to under-
stand your classes by obscuring where members are defined. The rule of
thumb is to limit use of static imports to things that are both universal and
pervasive in your application.
144 CLASS METHODS AND FIELDS
Incrementing
In the incrementCount method, you coded:
count = count + 1;
The right-hand side of the statement is an expression whose value is one plus
whatever value that count references. On execution, Java stores this new value
back into the count variable.
Adding a value to a variable is a common operation, so common that Java
supplies a a shortcut. The following two statements are equivalent:
count = count + 1;
count += 1;
The following line uses the decrement operator to decrease the value of
by one:
count
− −count;
You could code either of these examples with plus or minus signs after the
variable to increment:
count++;
count− −;
The results would be the same. When they are used as part of a larger ex-
pression, however, there is an important distinction between the prefix oper-
FACTORY METHODS 145
ator (when the plus or minus signs appear before the variable) and the
postfix operator (when the plus or minus signs appear after the variable).
When the Java VM encounters a prefix operator, it increments the variable
before it is used as part of a larger expression.
int i = 5;
assertEquals(12, ++i * 2);
assertEquals(6, i);
Recompile and retest (something you should have been doing all along).
Factory
Methods
Factory Methods
You can modify CourseSession so that it supplies a static-side factory method
to create CourseSession objects. By doing so, you will have control over what
happens when new instances of CourseSession are created.
Modify the CourseSessionTest method createCourseSession to demonstrate
how you will use this new factory method.
private CourseSession createCourseSession() {
return CourseSession.create("ENGL", "101", startDate);
}
In CourseSession, add a static factory method that creates and returns a new
CourseSession object:
public static CourseSession create(
String department,
String number,
Date startDate) {
return new CourseSession(department, number, startDate);
}
146 CLASS METHODS AND FIELDS
Find all other code that creates a CourseSession via new CourseSession(). Use
the compiler to your advantage by first making the CourseSession construc-
tor private:
private CourseSession(
String department, String number, Date startDate) {
// ...
4
Java allows you to code multiple constructors in a class. The descriptive names of
creation methods are far more valuable in this circumstance, since they help a client
developer determine which to choose.
5
[Kerievsky2004].
STATIC DANGERS 147
Simple Design
Software development purists will tell you that you could have saved a lot of
time by thinking through a complete design in the first place. With enough
foresight, you might have figured out that static creation methods were a
good idea and you would have put them in the code in the first place. Yes,
after considerable experience with object-oriented development, you will
learn how to start with a better design.
However, more often than not, the impact of design is not felt until you
actually begin coding. Designers who don’t validate their design in code fre-
quently produce an overblown system by doing things such as adding static
creation methods where they aren’t warranted. They also often miss impor-
tant aspects of design.
The best tactic to take is to keep your code as clean as possible at all
times. The rules to keep the design clean are, in order of importance:
• Make sure your tests are complete and always running 100 percent
green.
• Eliminate duplication.
• Ensure that the code is clean and expressive.
• Minimize the number of classes and methods. Static Dangers
The code should also have no more design in it than is necessary to sup-
port the current functionality. These rules are known as simple design.6
Simple design will give you the flexibility you need to update the design as
requirements change and as you require design improvements. Creating a sta-
tic factory method from a constructor wasn’t all that difficult, as you saw; it
is easily and safely done when you follow simple design.
Static Dangers
Using statics inappropriately can create significant defects that can be diffi-
cult to resolve. A classic novice mistake is to declare attributes as class vari-
ables instead of instance variables.
The class Student defines an instance variable named name. Each Student
object should have its own copy of name. By declaring name as static, every Stu-
dent object will use the same copy of name:
6
[Wiki2004b].
148 CLASS METHODS AND FIELDS
package sis.studentinfo;
A test can demonstrate the devastating effect this will have on your code:
package sis.studentinfo;
import junit.framework.*;
The last assertEquals statement will fail, since both studentA and studentB share
the class variable name. In all likelihood, other test methods will fail as well.
A mistake like this can waste a lot of your time, especially if you don’t
have good unit tests. Developers often figure that something as simple as a
variable declaration cannot be broken, so the variable declarations are often
the last place they look when there are problems.
Revert the Student class to eliminate the keyword static, remove testBad-
Static, recompile, and retest.
Garbage Collection
Java tries its best to manage your application’s use of memory as it executes. Mem-
ory is a precious, limited commodity in most computer systems. Every time your
code creates an object, Java must find memory space in which to store the object.
If Java did nothing to manage memory, objects put in memory would stay there
forever, and you would very quickly use up all available memory.
Java uses a technique known as garbage collection to manage your application’s
memory use. The Java VM tracks your use of all objects; from time to time it runs
something known as a garbage collector in the background. The garbage collector
reclaims objects that it knows you no longer need.
You no longer need an object when no other objects refer to that object. Sup-
pose you create an object within a method and assign it to a local variable (but not
to anything else). When the VM completes execution of the method, the object re-
mains in memory, but nothing points to it—the local variable reference is only
valid for the scope of the method. At this point, the object is eligible for garbage
collection and should disappear the next time the garbage collector runs (if ever—
you can never guarantee that the garbage collector will run).
If an instance variable refers to an object, you can give the object up for poten-
tial garbage collection by setting the value of the instance variable to null. Or you
can wait until the object containing the instance variable is no longer referred to.
Once nothing refers to an object, any objects it in turn refers to are also eligible for
garbage collection.
If you store an object in a standard collection, such as an ArrayList, the collec-
tion holds a reference to the object. The object cannot be garbage collected as long
the collection contains it.
Jeff’s Rule of
Statics
Don’t use statics until you know you need to use statics.
The simple rule comes about from observing first Java development ef-
forts. A little knowledge goes a long way. A little knowledge about statics
often leads developers to use them rampantly.
150 CLASS METHODS AND FIELDS
Booleans
The next small portion of the student information system that you
need to build is related to billing students for a semester. For now, the
amount that students are billed is based upon three things: whether or
not they are in-state students, whether or not they are full-time students, and
how many credit hours the students are taking. In order to support billing, you
will have to update the Student class to accommodate this information.
Students are either full-time or they are part-time. Put another way, stu-
Booleans
dents are either full-time or they are not full-time. Any time you need to rep-
resent something in Java that can be only in one of two states—on or
off—you can use a variable of the type boolean. For a boolean variable, there
are two possible boolean values, represented by the literals true (on) and false
(off). The type boolean is a primitive type, like int; you cannot send messages to
boolean values or variables.
Create a test method in the StudentTest class named testFullTime. It should
instantiate a Student, then test that the student is not full time. Full-time stu-
dents must have at least twelve credit hours; a newly created student has no
credit hours.
The return type of the method is boolean. By having this method return true, you
should expect that the test fails—the test asserts that isFullTime should return false.
Observe the test fail; modify the method to return false; observe the test pass.
The full-time/part-time status of a student is determined by how many
credits worth of courses that the student takes. To be considered full-
time, a student must have at least a dozen credits. How does a stu-
dent get credits? By enrolling in a course session.
The requirement now is that when a student is enrolled in a course
session, the student’s number of credits must be bumped up. Simplest
things first: Students need to be able to track credits, and a newly
created student has no credits.
public void testCredits() {
Student student = new Student("a");
assertEquals(0, student.getCredits());
student.addCredits(3);
assertEquals(3, student.getCredits());
student.addCredits(4);
assertEquals(7, student.getCredits());
}
Booleans
In Student:
package sis.studentinfo;
boolean isFullTime() {
return false;
}
int getCredits() {
return credits;
}
152 CLASS METHODS AND FIELDS
The Student constructor initializes the value of the credits field to 0, to meet
the requirement that newly created students have no credits. As you learned
in Lesson 2, you could have chosen to use field initialization, or to have not
bothered, since Java initializes int variables to 0 by default.
Up to this point, the student should still be considered part-time. Since the
number of credits is directly linked to the student’s status, perhaps you
should combine the test methods (but this is a debatable choice). Instead of
having two test methods, testCredits and testFullTime, combine them into a sin-
gle method named testStudentStatus.
public void testStudentStatus() {
Student student = new Student("a");
assertEquals(0, student.getCredits());
assertFalse(student.isFullTime());
student.addCredits(3);
assertEquals(3, student.getCredits());
assertFalse(student.isFullTime());
student.addCredits(4);
assertEquals(7, student.getCredits());
Booleans assertFalse(student.isFullTime());
}
In the case of assertEquals, often the default message is sufficient. In any case, try
reading the message generated and see if it imparts enough information to another
developer.
This test should pass. Now modify the test to enroll the student in a back-
breaking five-credit course in order to put them at twelve credits. You can
use the assertTrue method to test that the student is now full-time. A test passes
if the parameter to assertTrue is true, otherwise the test fails.
BOOLEANS 153
student.addCredits(3);
assertEquals(3, student.getCredits());
assertFalse(student.isFullTime());
student.addCredits(4);
assertEquals(7, student.getCredits());
assertFalse(student.isFullTime());
student.addCredits(5);
assertEquals(12, student.getCredits());
assertTrue(student.isFullTime());
}
The test fails. To make it pass, you must modify the isFullTime method to re-
turn true if the number of credits is 12 or more. This requires you to write a
conditional. A conditional in Java is an expression that returns a boolean
value. Change the method isFullTime in Student to include an appropriate ex-
pression:
boolean isFullTime() {
return credits >= 12;
}
Booleans
You can read this code as “return true if the number of credits is greater
than or equal to 12, otherwise return false.”
Refactor isFullTime to introduce a Student class constant to explain what
the number 12 means.
static final int CREDITS_REQUIRED_FOR_FULL_TIME = 12;
...
boolean isFullTime() {
return credits >= CREDITS_REQUIRED_FOR_FULL_TIME;
}
Now that students support adding credits, you can modify CourseSession
to ensure that credits are added to Student objects as they are enrolled. Start
with the test:
public class CourseSessionTest extends TestCase {
// ...
private static final int CREDITS = 3;
// ...
public void testEnrollStudents() {
Student student1 = new Student("Cain DiVoe");
session.enroll(student1);
assertEquals(CREDITS, student1.getCredits());
assertEquals(1, session.getNumberOfStudents());
assertEquals(student1, session.get(0));
Tests as Documentation
The test method testStudentStatus ensures that students report the appropriate
full-time or part-time status. It also ensures that the Student class correctly
adds credits.
What the test does not do is exhaustively test every possibility. The general
strategy for testing is to test against 0, 1, many, and any boundary conditions
and any exceptional cases. With respect to student credits, the test would en-
TESTS AS DOCUMENTATION 155
In the case of testStudentStatus, you as the developer have a high level of con-
fidence that the production code for isFullTime is valid. It’s only a single line,
and you know exactly what that line of code states:
return credits >= Student.CREDITS_REQUIRED_FOR_FULL_TIME
You might consider the test sufficient and choose to move on, and in
doing so you wouldn’t be entirely out of line. Again, tests are largely about
confidence. The less confident you are and the more complex the code, the
more tests you should write.
What about unexpected operations? Won’t it destroy the integrity of a
Student object if someone sends a negative value for number of credits? Re-
member, you are the developer building this system. You are the one who will
control access to the Student class. You have two choices: you can test for
and guard against every possible exceptional condition, or you can use the
design of your system to make some assumptions.
156 CLASS METHODS AND FIELDS
In the student information system, the CourseSession class will be the only
place where the Student number of credits can be incremented; this is by de-
sign. If CourseSession is coded properly, then it will have stored a reasonable
number of credits. Since it will have stored a reasonable number of credits,
there is theoretically no way for a negative number to be passed to Student.
You know that you have coded CourseSession properly because you did it
using TDD!
Of course, somewhere some human will have to enter that number of
credits for a course session into the system. It is at that point—at code that
represents the user interface level—that you must guard against all possibili-
ties. A human might enter nothing, a letter, a negative number, or a $ charac-
ter. The tests for ensuring that a reasonable positive integer is passed into the
system will have to be made at this point.
Once you have this sort of barrier against invalid data, you can consider
that the rest of the system is under your control—theoretically, of course.
With this assumption, you can remove much of the need to guard the rest of
your classes against bad data.
In reality, there are always “holes” that you unwittingly use to drop de-
fects in your code. But a large key to success with test-driven development is
to understand its heavy emphasis on feedback. A defect is an indication that
your unit tests were not complete enough: You missed a test. Go back and
write the missing test. Ensure that it fails, then fix it. Over time you will learn
Tests as
Documentation what is important to test and what is not. You will learn where you are in-
vesting too much time or too little time on the tests.
I have confidence in testStudentStatus and the corresponding implementation.
Where the test is lacking is in its ability to act as documentation. Part of the
problem is that you as a developer have too much knowledge about how you
have coded the functionality. It helps to find another developer to read the
test to see if it describes all of the business rules and boundaries properly. If
another developer is unavailable, take a step back and try to look at the test
as if you had never seen the underlying code. Does it tell you how to use the
class? Does it demonstrate the different scenarios? Does it indicate the con-
straints or limitations of the code being tested—perhaps by omission?
In this case, perhaps all that is needed is to let the test use the same con-
stant that Student uses. The test becomes a good deal more expressive by
virtue of doing so. The boundaries of full-time are now implicit and reason-
ably well understood.
student.addCredits(3);
assertEquals(3, student.getCredits());
assertFalse(student.isFullTime());
student.addCredits(4);
assertEquals(7, student.getCredits());
assertFalse(student.isFullTime());
student.addCredits(5);
assertEquals(Student.CREDITS_REQUIRED_FOR_FULL_TIME,
student.getCredits());
assertTrue(student.isFullTime());
}
More on Initialization
To support the notion of in-state versus out-of-state students, the
Student object needs to store a state that represents where the Stu-
dent resides. The school happens to be located in the state of Col-
orado (abbreviation: CO). If the student resides in any other state, or if the
student has no state specified (either the student is international or didn’t
complete the forms yet), then the student is an out-of-state student.
Here is the test: More on
Initialization
public void testInState() {
Student student = new Student("a");
assertFalse(student.isInState());
student.setState(Student.IN_STATE);
assertTrue(student.isInState());
student.setState("MD");
assertFalse(student.isInState());
}
The logic for determining whether a student is in-state will have to com-
pare the String representing the student’s state to the String "CO". This of
course means you will need to create a field named state.
boolean isInState() {
return state.equals(Student.IN_STATE);
}
To compare two strings, you use the equals method. You send the equals
message to a String object, passing another String as an argument. The equals
method will return true if both strings have the same length and if each string
matches character for character. Thus "CO".equals("CO") would return true;
"Aa".equals("AA") would return false.
158 CLASS METHODS AND FIELDS
In order for testInState to pass, it is important that the state field you create
has an appropriate initial value. Anything but "CO" will work, but the empty
String ("") will do just fine.
package sis.studentinfo;
You might also consider writing a test to demonstrate what should happen if
someone passes a lowercase state abbreviation. Right now, if client code passes
in "Co", it will not set the student’s status to in-state, since "Co" is not the same as
"CO". While that may be acceptable behavior, a better solution would be to al-
ways translate a state abbreviation to its uppercase equivalent prior to compar-
ing against "CO". You could accomplish this by using the String method toUpperCase.
Exceptions
Exceptions
What if you do not supply an initial value for the state field? In the test, you
create a new Student object and immediately send it the message isInState. The
isInState message results in the equals message being sent to the state field. Find
out what happens if you send a message to an uninitialized object. Change
the declaration of the state field to comment out the initialization:
private String state; // = "";
Then rerun the tests. JUnit will report an error, not a test failure. An error
occurs when neither your production code nor your test accounts for a prob-
lem. In this case, the problem is that you are sending a message to an unini-
tialized field reference. The second panel in JUnit shows the problem.
java.lang.NullPointerException
at studentinfo.Student.isInState(Student.java:37)
at studentinfo.StudentTest.testInState(StudentTest.java:39)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
...
REVISITING PRIMITIVE-TYPE FIELD INITIALIZATION 159
This is known as a stack walkback, or stack trace. It provides you with the
information on what went wrong, but figuring out the stack trace can take a
little bit of detective work. The first line in a stack trace tells you what the
problem is. In this case, you got something known as a NullPointerExcep-
tion. A NullPointerException is actually an error object, or exception, that is
“thrown” by some underlying, problematic code.
The rest of the lines in a stack trace “walk back” through the message sends
leading up to the error. Some of the lines refer to classes and methods that you
have coded; other lines refer to Java system library code and third-party library
code. The easiest way to decipher stack traces is to read down and find the first
line of code that you recognize as being “your” code. Then keep reading lines
up to the last line that you recognize. This last recognized line is the entry point
into your code; you will probably want to dig down from there.
In the above example, the last line to execute was line 37 in Student.java
(the line numbers in your code will likely differ). That code was invoked by
the message send in StudentTest line 39. So start with StudentTest line 39 in
order to follow the trail leading up to the trouble. That should take you to
the following line of code:
assertFalse(student.isInState());
Taking a look at line 37 in Student.java reveals the line of your code that
generated the NullPointerException:
Revisiting
return state.equals(Student.IN_STATE); Primitive-Type
Field
A reference that you do not explicitly initialize has a value of null. The Initialization
value null represents the unique instance of an object known as the null ob-
ject. If you send a message to the null object, you receive a NullPointerExcep-
tion. In this line of code, you sent the message equals to the uninitialized state
reference, hence the NullPointerException.
In Lesson 6, you will see how to determine if a field is null before attempt-
ing to send a message to it. For now, ensure that you initialize String fields to
the empty String ("").
Revert your code so that the state field is initialized properly. Rerun your
tests.
This includes boolean variables and variables of the numeric type (char and int,
plus the rest that you’ll learn about in Lesson 10: byte, short, long, float, and
double). Fields of the boolean type have an initial value of false. Each of the nu-
meric types has an initial value of 0.
Even though 0 is often a useful initial value for a numeric type, you should
explicitly initialize the field to 0 if that represents a meaningful value for the
field. For example, explicitly initialize a field to 0 if it represents a counter that
will track values starting at 0 when the Java VM instantiates the object. If you
are going to explicitly assign a more meaningful value to the field in later exe-
cution of the code, then you need not explicitly initialize the field to 0.
Provide explicit initializations only when necessary. This will help clarify
what you, the developer of the code, intend.
Exercises
1. Concatenating a new line character to the end of a string has created
repetitive code. Extract this functionality to a new utility method on
the class util.StringUtil. Mark StringUtil as a utility class by changing
its constructor’s access privilege to private. You will need to move the
Exercises
class constant NEWLINE to this class. Use your compiler to help you deter-
mine impacts on other code. Ensure that you have tests for the utility
method.
2. Transform your Pawn class into a more generic class named Piece. A
Piece is a color plus a name (pawn, knight, rook, bishop, queen, or
king). A Piece should be a value object: It should have a private con-
structor and no way to change anything on the object after construc-
tion. Create factory methods to return Piece objects based on the color
and name. Eliminate the ability to create a default piece.
3. Change BoardTest to reflect the complete board:
package chess;
import junit.framework.TestCase;
import util.StringUtil;
4. Ensure that a new Board creates sixteen black pieces and sixteen white
pieces. Use a class counter on the Piece object to track the counts.
Make sure you can run your tests twice without problems (uncheck
the “Reload Classes Every Run” box on JUnit and click Run a second
time).
5. Create methods isBlack and isWhite on the Piece class (test first, of
course).
6. Gather the names of each of your test methods. Put the class name in
front of each test name. Show the list to a friend and ask what the
methods say about each class.
7. Read over the section on Simple Design again. Does your current Exercises
chess design follow the patterns of simple design?
This page intentionally left blank
Lesson 5
Sorting: Preparation
The school needs a report of all course sessions. You must sort the Sorting:
report first by department, then by course number. This implies that Preparation
all courses for a given department will be listed together. The groups
of departments will be ordered in ascending alphabetical order. Within a de-
partment, the sessions will be ordered by course number.
To get started, get a simple report working of all course sessions. Don’t
worry yet about its order.
package sis.report;
import junit.framework.*;
import java.util.*;
import sis.studentinfo.*;
import static sis.report.ReportConstant.NEWLINE;
163
164 INTERFACES AND POLYMORPHISM
assertEquals(
"ENGL 101" + NEWLINE +
"CZEC 200" + NEWLINE +
"ITAL 410" + NEWLINE,
report.text());
}
}
The report is bare boned, showing simply a course department and num-
ber on each line. Currently the report lists sessions in the order in which you
added them to the CourseReport object.
The production class, CourseReport, looks similar to RosterReporter:
package sis.report;
import java.util.*;
import sis.studentinfo.*;
import static sis.report.ReportConstant.NEWLINE;
Sorting: Collections.sort
You can sort a list of String objects quite simply, as this language test demon-
strates:
COURSEREPORTTEST 165
The static sort method in the java.util.Collections class takes a list as a pa-
rameter and sorts the list in place.1 If you do not want to sort the list in
place—if you do not want to modify the original list—you can create a new
list and send that list as a parameter to the sort message.
public void testSortStringsInNewList() {
ArrayList<String> list = new ArrayList<String>();
list.add("Heller");
list.add("Kafka");
list.add("Camus");
list.add("Boyle");
ArrayList<String> sortedList = new ArrayList<String>(list);
java.util.Collections.sort(sortedList);
assertEquals("Boyle", sortedList.get(0));
assertEquals("Camus", sortedList.get(1));
assertEquals("Heller", sortedList.get(2));
assertEquals("Kafka", sortedList.get(3));
assertEquals("Heller", list.get(0));
assertEquals("Kafka", list.get(1)); CourseReport
assertEquals("Camus", list.get(2)); Test
assertEquals("Boyle", list.get(3));
}
The last four assertions verify that the original list remains unmodified.
CourseReportTest
In CourseReportTest, modify the assertion in testReport to ensure that you pro-
duce the report in sorted order. Your test data currently takes only the de-
partment into account. That’s fine for now:
1
The sort method uses a merge sort algorithm that splits the list of elements into two
groups, recursively sorts each group, and merges all sorted subgroups into a complete
list.
166 INTERFACES AND POLYMORPHISM
assertEquals(
"CZEC 200" + NEWLINE +
"ENGL 101" + NEWLINE +
"ITAL 410" + NEWLINE,
report.text());
The test fails as expected. You should be able to see in JUnit that the sessions
are in the wrong order.
To solve the problem, use Collections.sort to order the sessions list.
public String text() {
Collections.sort(sessions);
StringBuilder builder = new StringBuilder();
for (CourseSession session: sessions)
builder.append(
session.getDepartment() + " " +
session.getNumber() + NEWLINE);
return builder.toString();
}
The source of the error can be difficult to decipher, even if you are rela-
tively experienced in Java. The problem is that the method declaration for
sort requires the objects it sorts to be of the type java.lang.Comparable. (How
it does that involves advanced syntax that I will discuss in Lesson 14 on
Interfaces generics.)
Comparable is a type that allows you to compare objects to one another.
But you want to compare CourseSession objects, since that’s what you bound
the variable sessions to. The secret to getting this to work takes advantage of a
feature in Java known as interfaces. Interfaces allow an object to act as more
than one type. You will modify CourseSession to act as a java.lang.Compara-
ble type in addition to being a CourseSession.
Interfaces
Comparable is an interface type, not a class type. An interface contains any
number of method declarations. A method declaration is the signature for a
method followed by a semicolon. There are no braces and there is no method
code. Java defines the type Comparable as follows:
INTERFACES 167
<<interface>>
Comparable
compareTo(:Object) : int
If you look further into the source for the String class, you will find the
implementation for the compareTo method that Comparable declares.
For the sort invoked by CourseReport to work, it must be able to send the
message compareTo to the objects contained within sessions. The sort method is
unaware of the class of objects in the collection, however. It takes each object
in turn and tries to assign the object to a variable of the Comparable inter-
face type. When this assignment is successful, the sort can safely send the
compareTo message to the object. When the assignment is not successful, Java
generates an error.
Why Interfaces
Interfaces are a very powerful and very important feature in Java. Part of the
key to solid design in Java is knowing when (and when not) to use interfaces.
Used properly, interfaces help you compartmentalize your software to help
minimize impacts on other parts of the code.
Interfaces provide higher levels of abstraction. The sort code doesn’t need
Why Interfaces to know any details about the objects it is sorting. It need not know whether
it is sorting Student objects, Customer objects, or Strings. All the sort code
needs to know is that the objects it is sorting support being compared to
other objects. The sort code knows only about this more abstract quality, not
any other specifics of the objects it is sorting.
You can view this concept of abstracting sortability as a way to eliminate
duplication. Interfaces allow a sort algorithm to operate on objects of differ-
ent base types. The sort algorithm needs no repetitive code to determine the
types of objects it compares.
Use interfaces to provide abstraction layers in your system and to
help remove duplication.
The abstraction layers in your system isolate your code from nega-
tive effects. Ideally, you write the sort class once, get it to work perfectly, and
then close it off to any future changes. The only detail the sort class has to
know about the objects it sorts is that they respond to the compareTo method by
IMPLEMENTING COMPARABLE 169
returning an int. The classes of those objects can change in other myriad
ways, but none of these changes will ever impact your sort code.
You can use interfaces can break dependencies on code that doesn’t work
or doesn’t even exist by writing stub code to meet the specifications of the in-
terface. In Lesson 12, you will learn how to accomplish this using a tech-
nique known as mocking. Interfaces are an essential tool for effective testing.
By implementing the Comparable interface, the String class publicizes the
fact that Strings can be compared for sort purposes. The String supplies code
in the compareTo method to determine how String objects are compared.
In order to get the sort to work for the list of sessions, you must modify
CourseSession to implement the Comparable interface.
Implementing Comparable
The job of the compareTo method in the Comparable interface is to indicate
which of two objects—the message receiver and the message parameter—
should appear first. The code you will supply in compareTo should result in what
is known as the natural, or default, sort order for CourseSessions. In other
words, how do you normally want to sort a collection of CourseSession
objects?
The compareTo method must return an int value. If this return value is 0, then
the two objects are equal for purposes of sorting. If the return value is nega-
tive, then the receiver (the object to which code sends the compareTo message)
should come before the parameter in the sort order. If the value is positive, Implementing
then the parameter should come before the receiver in the sort order. Comparable
The String class implements the compareTo method in order to sort strings in
alphabetical order. The following language test demonstrates the values re-
turned from the compareTo method in the three possible scenarios.
public void testStringCompareTo() {
assertTrue("A".compareTo("B") < 0);
assertEquals(0, "A".compareTo("A"));
assertTrue("B".compareTo("A") > 0);
}
You will need to modify the CourseSession class to implement the Compa-
rable interface and supply a definition for compareTo. An appropriate test to add
to CourseSessionTest:
public void testComparable() {
final Date date = new Date();
CourseSession sessionA = CourseSession.create("CMSC", "101", date);
170 INTERFACES AND POLYMORPHISM
More on this
In the Student compareTo method, the return statement includes the expression
this.getDepartment(). As you learned in Lesson 1, the this reference is to the current
object. Scoping method calls with this is usually unnecessary, but in this case it
helps differentiate the current object and the parameter object.
Another use of the this keyword that you haven’t seen is for constructor chain-
ing. You can invoke another constructor defined on the same class by using a
slightly different form of this:
class Name {
...
public Name(String first, String mid, String last) {
Implementing
this.first = first;
Comparable
this.mid = mid;
this.last = last;
}
The single statement in the second constructor of Name calls the first construc-
tor. The goal in this example is to pass a default value of the empty string as the
middle name.
Such a call to another constructor must appear as the first line of a constructor.
Constructor chaining can be a valuable tool for helping you eliminate duplica-
tion. Without chaining, you would need to write a separate method to do common
initialization.
SORTING ON DEPARTMENT AND NUMBER 171
For the compareTo method implementation, you are returning the result of
comparing the current CourseSession’s department (this.getDepartment()) to the
parameter CourseSession object’s department (that.getDepartment()).
At this point, both testComparable in CourseSessionTest and testReport in
CouseReportTest should pass.
import junit.framework.*;
Sorting on
import java.util.*;
Department and
import sis.studentinfo.*;
Number
import static sis.report.ReportConstant.NEWLINE;
assertEquals(
"CZEC 200" + NEWLINE +
"CZEC 220" + NEWLINE +
"ENGL 101" + NEWLINE +
"ITAL 330" + NEWLINE +
172 INTERFACES AND POLYMORPHISM
The if Statement
You use the if statement for conditional branching: If a condition holds true,
execute one branch (section) of code, otherwise execute another branch of
code. A branch can be a single statement or a block of statements.
Flesh out the CourseSessionTest method testCompareTo by adding tests to rep-
resent more complex comparisons:
public void testComparable() {
final Date date = new Date();
CourseSession sessionA = CourseSession.create("CMSC", "101", date);
CourseSession sessionB = CourseSession.create("ENGL", "101", date);
assertTrue(sessionA.compareTo(sessionB) < 0);
assertTrue(sessionB.compareTo(sessionA) > 0);
pare this course number to the parameter’s course number and assign the re-
sult to compare. Regardless, return the value stored in the compare local variable.
You could have also written the code as follows:
public int compareTo(CourseSession that) {
int compare =
this.getDepartment().compareTo(that.getDepartment());
if (compare != 0)
return compare;
return this.getNumber().compareTo(that.getNumber());
}
If, on the first comparison, the departments are unequal (the result of the
comparison is not 0), then that’s all that needs to happen. You can stop there
and return the result of the comparison. The rest of the code is not executed.
This style of coding can be easier to follow. Be cautious, however: In longer
methods, multiple returns can make the method more difficult to understand
and follow. For longer methods, then, I do not recommend multiple return
statements. But the real solution is to have as few long methods as possible.
All tests should pass.
Grading Students
You need to be able to produce a report card for all students. Before
you can do that, you will need the capability to calculate the GPA
(grade point average) for a given student. A student receives a num-
ber of grades. You store a student’s grade by sending the message addGrade to a Floating-Point
Numbers
Student object, passing along the letter grade. When asked for the GPA, the
student uses its stored grades to calculate a GPA.
You will supply grades as decimal numbers, known in Java as floating-
point numbers.
Floating-Point Numbers
You are already familiar with the primitive-type int, representing integer val-
ues. In addition to integer numerics, Java supports IEEE 754 32-bit single
precision binary floating point format numbers, also known as float points,
or floats. Java also supports 64-bit double precision floats, also known as
doubles.
174 INTERFACES AND POLYMORPHISM
Table 5.1 lists the types, the range of values that they support and some
examples.
Any numeric literal with a decimal point or any numeric literal with a suf-
fix of f, F, d, or D, is by definition a floating-point number.
Float and double literals may also use scientific notation. Any number
with an e or E in the middle is by definition a floating-point number. The dou-
ble numeric literal 1.2e6 represents 1.2 times 10 to the 6th power, or 1.2x106
in standard mathematical notation.
If you do not supply a suffix for a floating-point number, it is by default a
double precision float. In other words, you must supply an F or f suffix to
produce a single precision float literal. In Agile Java, I will prefer the use of
double over float due to its higher precision and the fact that I need not supply
a suffix.
Realize, though, that floating-point numbers are approximations of real
numbers based on bit patterns. It is not possible to represent all real num-
bers, since there are an infinite amount of real numbers and only a finite set
Floating-Point of possible representations in either 32- or 64-bit precision floats. This
Numbers
means that most real numbers have only an approximate floating-point
representation.
For example, if you execute the following line of code:
value = 0.8999999999999999
Testing Grades
The following test, which you should add to StudentTest, demonstrates a range
of possibilities. It starts with the simplest case: The student has received no
grades, in which case the student’s GPA should be 0. It also tests grade combi-
nations based on applying each of the possible letter grades from A through F.
private static final double GRADE_TOLERANCE = 0.05;
...
public void testCalculateGpa() {
Student student = new Student("a");
assertEquals(0.0, student.getGpa(), GRADE_TOLERANCE);
student.addGrade("A");
assertEquals(4.0, student.getGpa(), GRADE_TOLERANCE);
student.addGrade("B");
assertEquals(3.5, student.getGpa(), GRADE_TOLERANCE);
student.addGrade("C");
assertEquals(3.0, student.getGpa(), GRADE_TOLERANCE);
student.addGrade("D");
assertEquals(2.5, student.getGpa(), GRADE_TOLERANCE);
student.addGrade("F");
assertEquals(2.0, student.getGpa(), GRADE_TOLERANCE);
}
The assertEquals method calls in this test show three parameters instead of
two. Since floating-point numbers are not precise representations of real
numbers, there is a possibility that a calculated value may be off from an ex-
pected value by a certain amount. For example, were it possible to code:
assertEquals(0.9, 3 * 0.3);
Testing Grades
the test would fail:
AssertionFailedError: expected:<0.9> but was:<0.8999999999999999>
JUnit provides a third parameter when you need to compare two floating-
point values for equality. This parameter represents a tolerance: How much
can the two floating-point values be off by before JUnit reports an error?
A general rule of thumb is that values should be off by no more than half
of the smallest precision you are interested in representing accurately. For ex-
ample, if you are working with cents (hundredths of a dollar), then you want
to ensure that compared amounts are off by no more than half a cent.
You want to accurately express GPAs to tenths of a point; therefore your
tolerance should be 5/100 of a point. The above code defines a static con-
stant GRADE_TOLERANCE local to the StudentTest class and uses it as the third para-
meter to each assertEquals method:
assertEquals(2.0, student.getGpa(), GRADE_TOLERANCE);
176 INTERFACES AND POLYMORPHISM
In order to make the test pass, you will need to change the Student class to
store each added grade in an ArrayList.2 You can then code the getGpa method
to calculate the result GPA. To do so, you must first iterate through the list of
grades and obtain a grade point total. You then divide this grade point total
by the total number of grades to get the GPA.
import java.util.*;
...
class Student {
private ArrayList<String> grades = new ArrayList<String>();
...
void addGrade(String grade) {
grades.add(grade);
}
...
double getGpa() {
if (grades.isEmpty())
return 0.0;
double total = 0.0;
for (String grade: grades) {
if (grade.equals("A")) {
total += 4;
}
else {
if (grade.equals("B")) {
total += 3;
}
else
{
if (grade.equals("C")) {
total += 2;
Testing Grades }
else {
if (grade.equals("D")) {
total += 1;
}
}
}
}
}
return total / grades.size();
}
}
2
You could also calculate the GPA as each grade is added.
TESTING GRADES 177
and returns upon a special condition is known as a guard clause. The guard
clause in getGpa guards the remainder of the method from the special case
where there are no grades. Since you calculate a GPA by dividing by the
number of grades, the guard clause eliminates any possibility of dividing by
zero.
The method code then uses a for-each loop to extract each grade from the
grades collection. The body of the for-each loop compares each grade against
the possible letter grades. The body code uses an extension of the if state-
ment known as the if-else statement.
A paraphrasing of the body of the for-each loop: If the grade is an A, add 4
to the total,3 otherwise, execute the block of code (or single statement) ap-
pearing after the else keyword. In this case, the block of code is itself com-
prised of another if-else statement: If the grade is a B, add 3 to the total,
otherwise execute yet another block of code. This pattern continues until all
possibilities are exhausted (an F is ignored, since it adds nothing to the grade
total).
Complex if-else statements can be difficult to read because of the many
curly braces and repeated indenting. In the circumstance where you have
repetitive nested if-else statements, you can use a tighter formatting style.
Each else-if statement combination goes on the same line. Also, you can elim-
inate the braces in this example, since the body of each if statement is a sin-
gle line.
double getGpa() {
if (grades.isEmpty())
return 0.0;
double total = 0.0; Testing Grades
for (String grade: grades) {
if (grade.equals("A"))
total += 4;
else if (grade.equals("B"))
total += 3;
else if (grade.equals("C"))
total += 2;
else if (grade.equals("D"))
total += 1;
}
return total / grades.size();
}
This alternative formatting style makes the getGpa method far easier to read
but produces the same results.
3
You may add int values to doubles with no ill effects. However, mixing int and double
values in an expression can cause unintended effects. See Lesson 10 for a discussion
of numerics in Java.
178 INTERFACES AND POLYMORPHISM
Refactoring
The method getGpa is still a bit long and difficult to follow. Extract from it a
new method that returns a double value for a given letter grade:
double getGpa() {
if (grades.isEmpty())
return 0.0;
double total = 0.0;
for (String grade: grades)
total += gradePointsFor(grade);
return total / grades.size();
}
Make sure this compiles and passes the tests. One further change that you
can make is to remove the else clauses. A return statement is a flow of control.
Once the Java VM encounters a return statement in a method, it executes no
Refactoring
more statements within the method.
Enums
J2SE 5.0 introduces the notion of an enumerated type—a type that constrains
all possible values to a discrete list. For example, if you implement a class
that represents a deck of cards, you know that the only possible suit values
are clubs, diamonds, spades, and hearts. There are no other suits. For grades,
you know there are only five possible letter grades.
You can define grades as strings, as in the above GPA code, and use String
values to represent possible letter grades. While this will work, it has some Enums
problems. First, it is easy to make a mistake when typing all the various
strings. You can create class constants to represent each letter grade (which
we probably should have done in the above code), and as long as all code
uses the constants, things are fine.
Even if you have supplied class constants, client code can still pass an in-
valid value into your code. There is nothing that prevents a user of the Stu-
dent class from executing this line of code:
student.addGrade("a");
The results are probably not what you expect—you wrote the code to
handle only uppercase grade letters.
You can instead define an enum named Grade. The best place for this, for
now, is within the Student class:
public class Student {
enum Grade { A, B, C, D, F };
...
180 INTERFACES AND POLYMORPHISM
Using enum results in the declaration of a new type. In this case, you have
just created a new type named Student.Grade, since you defined the Grade
enum within the Student class. The new enum has five possible values, each
representing a named object instance.
Change the test to use the enum instances:
In the Student class, you will need to change any code that declared a
grade to be of the type String to now be of the type Grade. Since the Grade
enum is defined within Student, you can refer to it directly (i.e., as Grade in-
stead of Student.Grade).
return 0;
}
...
}
The method gradePointsFor compares the value of the parameter grade to each of
the enum values in turn. Each of the enum values represents a unique instance in
memory. This allows you to make the comparison using the == operator instead
of the equals method. Remember that the == operator is used for comparing two
object references: Do the two references point to the same object in memory?
It is no longer possible for a client to pass in an invalid value to the
method addGrade. Client code cannot create a new instance of Grade. The five
instances specified in the enum declaration within Student are all that exist.
Polymorphism
Overuse of the if statement can quickly turn your code into a high-mainte-
nance legacy that is difficult to follow. A concept known as polymorphism
can help structure your code to minimize the need for if statements.
Student grading is a bit more involved than the above example.
You need to support grading for honors students. Honors students
are graded on a higher scale. They can earn five grade points for an
A, 4 points for a B, 3 points for a C, and 2 points for a D.
A highly refactored test:
public void testCalculateHonorsStudentGpa() { Polymorphism
assertGpa(createHonorsStudent(), 0.0);
assertGpa(createHonorsStudent(Student.Grade.A), 5.0);
assertGpa(createHonorsStudent(Student.Grade.B), 4.0);
assertGpa(createHonorsStudent(Student.Grade.C), 3.0);
assertGpa(createHonorsStudent(Student.Grade.D), 2.0);
assertGpa(createHonorsStudent(Student.Grade.F), 0.0);
}
. . . but things are becoming unwieldy. Next, suppose you must support a dif-
ferent grading scheme:
double gradePointsFor(Grade grade) {
if (isSenatorsSon) {
if (grade == Grade.A) return 4;
if (grade == Grade.B) return 4;
if (grade == Grade.C) return 4;
Polymorphism
if (grade == Grade.D) return 4;
return 3;
}
else {
double points = basicGradePointsFor(grade);
if (isHonors)
if (points > 0)
points += 1;
return points;
}
}
Now the code is getting messy. And the dean has indicated that there are
more schemes on the way, in this politically correct age of trying to be every-
thing to everyone. Every time the dean adds a new scheme, you must change
the code in the Student class. In changing Student, it is easy to break the class
and other classes that depend on it.
You would like to close the Student class to any further changes. You
know that the rest of the code in the class works just fine. Instead of chang-
POLYMORPHISM 183
<<interface>>
GradingStrategy
Student
getGradePointsFor(grade: Grade) : int
ing the Student class each time the dean adds a new scheme, you want to sup-
port the new requirement by extending the system.4
Design your system to accommodate changes through extension,
not modification.
You can consider the grading scheme to be a strategy that varies based on
the type of student. You could create specialized student classes, such as
HonorsStudent, RegularStudent, and PrivilegedStudent, but students can
change status. Changing an object from being one type to being a different
type is difficult.
Instead, you can create a class to represent each grading scheme. You can
then assign the appropriate scheme to each student. Polymorphism
You will use an interface, GradingStrategy, to represent the abstract con-
cept of a grading strategy. The Student class will store a reference of the in-
terface type GradingStrategy, as shown in the UML diagram in Figure 5.2. In
the diagram, there are three classes that implement the interface: Regu-
larGradingStrategy, HonorsGradingStrategy, and EliteGradingStrategy. The
closed-arrow relationship shown with a dashed line is known as the realizes
association in UML. In Java terms, RegularGradingStrategy implements the
GradingStrategy interface. In UML terms, RegularGradingStrategy realizes
the GradingStrategy interface.
The GradingStrategy interface declares that any class implementing the in-
terface will provide the ability to return the grade points for a given grade.
You define GradingStrategy in a separate source file, GradingStrategy.java, as
follows:
4
[Martin2003], p. 99.
184 INTERFACES AND POLYMORPHISM
Comparable
String
package sis.studentinfo;
You represent each strategy with a separate class. Each strategy class im-
Polymorphism plements the GradingStrategy interface and thus provides appropriate code
for the getGradePointsFor method.
An interface defines methods that must be part of the public interface of
the implementing class. All interface methods are thus public by definition. As
such, you need not specify the public keyword for method declarations in an
interface:
package sis.studentinfo;
HonorsGradingStrategy:
package sis.studentinfo;
RegularGradingStrategy:
package sis.studentinfo;
If you’re savvy enough to spot the duplication in this code, take the time
to refactor it away. You could introduce a BasicGradingStrategy and have it
supply a static method with the common code. Or you can wait (only be-
Polymorphism
cause I said so—otherwise never put off eliminating duplication!): The next
lesson introduces another way of eliminating this duplication.
In StudentTest, instead of using setHonors to create an honors student, you
send the message setGradingStrategy to the Student object. You pass an Honors-
GradingStrategy instance as the parameter.
Note how you were able to make this change in one place by having elimi-
nated all duplication in the test code!
You then need to update the Student class to allow the strategy to be set
and stored. Initialize the gradingStrategy instance variable to a RegularGrad-
ingStrategy object to represent the default strategy.
186 INTERFACES AND POLYMORPHISM
The next section, Using Interface References, explains why you can de-
clare both the gradingStrategy instance variable and the parameter to setGrad-
ingStrategy as the type GradingStrategy.
Modify the Student code to obtain grade points by sending the gradePointsFor
message to the object stored in the gradingStrategy reference.
Eliminate the instance variable isHonors, its associated setter method, and
the method basicGradePointsFor.
Now that the gradePointsFor method does nothing but a simple delegation,
you can inline its code to the getGpa method. In some cases, single-line delega-
tion methods such as gradePointsFor can provide cleaner, clearer code, but in this
case, the gradePointsFor method adds neither. Its body and name say the same
thing, and no other code uses the method.
Inline the gradePointsFor method by replacing the call to it (from getGPA) with
the single line of code in its body. You then can eliminate the gradePointsFor
Polymorphism
method entirely.
double getGpa() {
if (grades.isEmpty())
return 0.0;
double total = 0.0;
for (Grade grade: grades)
total += gradingStrategy.getGradePointsFor(grade);
return total / grades.size();
}
B XImplementation
5
[Martin2003], p. 127.
EXERCISES 189
The Java API documentation shows that ArrayList implements the List in-
terface. If you browse the documentation for the List interface, you’ll see that
virtually all messages you had sent to ArrayList instances are defined in List.
Thus, you should prefer defining students (in this example) as a List type:
For now, go through your source code and change as many references as
possible to be an interface type.
Exercises
1. Create an enumeration for the two colors. Encapsulate the enumera-
tion so that only the Piece class is aware of its existence. Depending
on your implementation, this may require significant refactoring.
Note each place you have to change in order to introduce the
change—How could you have removed the need for any changes you
felt were done twice?
190 INTERFACES AND POLYMORPHISM
You might consider also creating an enumeration for the piece val-
ues. If your code allows you to do so easily, go ahead. You may want
to wait until Lesson 6, where you will learn how to associate data
with enumeration values.
2. Introduce an enum for each piece. You will want to divorce the piece
representation from the piece enum.
3. Create a separate factory method on Piece for each color/piece combi-
nation (e.g., createWhitePawn, createBlackRook).
package pieces;
import junit.framework.TestCase;
assertTrue(blackPiece.isBlack());
assertEquals(type, blackPiece.getType());
assertEquals(Character.toUpperCase(representation),
blackPiece.getRepresentation());
}
}
4. Write code in Board to return the number of pieces, given the color
and piece representation. For example, the below board should return
3 if you ask it for the number of black pawns. Calculate the count on
demand (in other words, do not track the counts as you create pieces).
. K R . . . . .
P . P B . . . .
. P . . Q . . .
. . . . . . . .
. . . . . n q .
. . . . . p . .
. . . . . . p .
. . . . r k . .
In the next exercise, you will create the ability to place pieces on an
empty board. Start by adding a test that verifies that a newly instanti-
ated Board object contains no pieces. This will lead you to a bit of
refactoring. You may need to replace use of the add method on Ar-
rayList with the set method.
6. Create the code necessary to place pieces at arbitrary positions on the
board. Make sure you refactor your solution with earlier code you’ve
written to manage squares.
. . . . . . . . 8 (rank 8)
. . . . . . . . 7
. K . . . . . . 6
. R . . . . . . 5
. . k . . . . . 4
. . . . . . . . 3
. . . . . . . . 2
192 INTERFACES AND POLYMORPHISM
. . . . . . . . 1 (rank 1)
a b c d e f g h
files
For the example shown, black would have a strength of 20, and white
would have a strength of 19.5.
Develop your solution incrementally. Start with a single piece on
the board. Add pieces to the board one by one, altering your asser-
tions each time. Finally, add the complex scenario of determining if
another pawn is in the same file.
8. As you loop through all pieces on the board, assign the strength of
Exercises
each piece to the Piece object itself. For each side (black and white),
gather the list of pieces in a collection. Ensure that the collection is
sorted in order from the piece with the highest value to the piece with
the lowest value.
9. Examine your code for an opportunity to create an interface. Does the
system feel cleaner or more cluttered? Be wary of introducing an inter-
face without need—remember that simplicity requires “fewest classes,
fewest methods” shortly after “no duplication.”
10. Go through the code you have produced so far and look for opportu-
nities to use early returns and guard clauses to simplify your code.
11. Go through the code you have produced so far and ensure that you
are using interfaces rather than concrete implementations wherever
possible. In particular, examine your usages of collections and ensure
you are programming to the interfaces.
EXERCISES 193
Also: While the static import facility can make code more difficult
to follow, one appropriate place you can use it is in test code. Test
code generally interacts with a single target class. If the target class
contains class constants, you can use static import in the test class for
these constants. The context should make it clear as to where the con-
stants are defined.
Alter your code to make judicious use of import static.
Exercises
This page intentionally left blank
Lesson 6
Inheritance
195
196 INHERITANCE
You then specify any number of case labels. Each case label specifies a single
enum value. When the Java VM executes the switch statement, it determines the
value of the target expression. It compares this value to each case label in
turn.
If the target value matches a case label, the Java VM transfers control to the
statement immediately following that case label. It skips statements between
the switch target and the case label. If the target value matches no case label, the
Java VM transfers control to the default case label, if one exists. The default case
label is optional. If no default case label exists, the VM transfers control to the
statement immediately following the switch statement.
Thus, if the value of the grade variable is Student.Grade.B, Java transfers
control to the line that reads:
case B: return 3;
1
You’ll learn more about statements to transfer control in Lesson 7.
CASE LABELS ARE JUST LABELS 197
int totalPoints = 0;
switch (score) {
case fieldGoal:
totalPoints += 3;
case touchdown:
totalPoints += 6;
case extraPoint:
totalPoints += 1;
case twoPointConversion:
totalPoints += 2;
case safety:
totalPoints += 2;
}
assertEquals(6, totalPoints);
}
Since score was set to Score.touchdown, the Java VM transferred control to the line
following the corresponding case label:
case touchdown:
totalPoints += 6;
Once Java executed that line, it then executed the next three statements (and
ignored the case labels): Case Labels Are
Just Labels
case extraPoint: // ignored
totalPoints += 1;
case twoPointConversion: // ignored
totalPoints += 2;
case safety: // ignored
totalPoints += 2;
twoPointConversion, safety };
int totalPoints = 0;
switch (score) {
case fieldGoal:
totalPoints += 3;
break;
case touchdown:
totalPoints += 6;
break;
case extraPoint:
totalPoints += 1;
break;
case twoPointConversion:
totalPoints += 2;
break;
case safety:
totalPoints += 2;
break;
}
assertEquals(6, totalPoints);
}
switch (score) {
case fieldGoal:
totalPoints += 3;
Case Labels Are break;
Just Labels case touchdown:
totalPoints += 6;
break;
case extraPoint:
totalPoints += 1;
break;
case twoPointConversion:
case safety:
totalPoints += 2;
break;
}
In addition to switching on enum values, you can switch on char, byte, short, or
intvalues. You cannot, unfortunately, switch on String values.
Your code should not contain a lot of switch statements. Also, it should not
contain lots of multiple if statements that accomplish the same goal. Often,
you can eliminate switch statements in favor of polymorphic solutions, as in
the previous lesson. Your main guides as to whether you should replace switch
statements with polymorphism are duplication and frequency/ease of mainte-
nance.
Before continuing, refactor RegularGradingStrategy to use a switch state-
ment instead of multiple if statements.
Maps
Yet another alternative to using a switch statement is to use a map. A map is a
collection that provides fast insertion and retrieval of values associated with
specific keys. An example is an online dictionary that stores a definition (a
value) for each word (key) that appears in it.
Java supplies the interface java.util.Map to define the common behavior
for all map implementations. For the report card messages, you will use the
EnumMap implementation. An EnumMap is a Map with the additional con-
straint that all keys must be enum objects.
To add a key-value pair to a Map, you use the put method, which takes the
key and value as parameters. To retrieve a value stored at a specific key, you
use the get method, which takes the key as a parameter and returns the asso-
ciated value.
Suppose you need to print an appropriate message on a report card for
each student, based on their grade. Add a new class named ReportCardTest
Maps
to the sis.reports package.
package sis.report;
import junit.framework.*;
import sis.studentinfo.*;
card.getMessage(Student.Grade.C));
assertEquals(ReportCard.D_MESSAGE,
card.getMessage(Student.Grade.D));
assertEquals(ReportCard.F_MESSAGE,
card.getMessage(Student.Grade.F));
}
}
Change the Grade enum defined in Student to public to make this code
compile.
The ReportCard class:
package sis.report;
import java.util.*;
import sis.studentinfo.*;
The getMessages method uses lazy initialization (see the sidebar) to load the
appropriate message strings, which you define as static constants, into a new
instance of the EnumMap.
Lazy Initialization
You have learned to initialize fields either where you declared them or within a
constructor. Another option is to wait until you first need to use a field and then
initialize it. This is a technique known as lazy initialization.
Within a getter method, such as getMessages, you first test to see whether or not a
field has already been initialized. For a reference instance variable, you test
whether or not it is null. If it is null, you do whatever initialization is necessary and
assign the new value to the field. Regardless, you return the field as the result of
the getter, like you normally would.
The primary use of lazy initialization is to defer the potentially costly operation
of loading a field until it is necessary. If the field is never needed, you never expend
the cycles required to load it. A minor amount of overhead is required to test the
field each time it is accessed. This overhead is negligible for occasional access.
Here, lazy initialization is used as a way of keeping more-complex initialization
logic close to the field access logic. Doing so can improve understanding of how
the field is initialized and used.
The getMessage method is a single line of code that uses the Map method get
to retrieve the appropriate string value for the grade key.
Maps are extremely powerful, fast data structures that you might use fre-
quently in your applications. Refer to Lesson 9 for a detailed discussion of
their use.
Inheritance
Inheritance
The code for RegularGradingStrategy and HonorsGradingStrategy contains
duplicate logic. The code that you used to derive a basic grade is the same in
both classes—both classes contain switch statements with the exact same
logic:
switch (grade) {
case A: return 4;
case B: return 3;
case C: return 2;
case D: return 1;
default: return 0;
}
202 INHERITANCE
// RegularGradingStrategy.java
package sis.studentinfo;
case A: return 4;
case B: return 3;
case C: return 2;
case D: return 1;
default: return 0;
}
}
}
You can now move the common code to a method on the BasicGrading-
Strategy superclass (also known as the base class). First, move the method
basicGradePointsFor from HonorsGradingStrategy to BasicGradingStrategy:
// HonorsGradingStrategy.java
package sis.studentinfo;
// BasicGradingStrategy.java
package sis.studentinfo;
HonorsGradingStrategy
package sis.studentinfo;
Abstract
Classes Abstract Classes
You could stop here and have a sufficient solution. You could also go one
step further and eliminate a bit more duplication by having the superclass
BasicGradingStrategy implement the GradingStrategy interface directly.
The only problem is, you don’t necessarily have an implementation for
getGradePointsFor.
Java allows you to define a method as abstract, meaning that you cannot or
do not wish to supply an implementation for it. Once a class contains at least
one abstract method, the class itself must similarly be defined as abstract. You
cannot create new instances of an abstract class, much as you cannot directly
instantiate an interface.
ABSTRACT CLASSES 205
Any class extending from an abstract class must either implement all inher-
ited abstract methods, otherwise it too must be declared as abstract.
Change the declaration of BasicGradingStrategy to implement the Grading-
Strategy interface. Then supply an abstract declaration for the getGradePointsFor
method:
package sis.studentinfo;
The subclasses no longer need to declare that they implement the Grad-
ingStrategy interface:
// HonorsGradingStrategy.java
package sis.studentinfo;
// RegularGradingStrategy.java
package sis.studentinfo;
Extending Methods
A third solution2 is to recognize that the RegularGradingStrategy is virtually
the same as BasicGradingStrategy. The method getGradePointsFor as defined in
HonorsGradingStrategy is basically an extension of the method in Regu-
larGradingStrategy—it does what the base class method does, plus a little
more. In other words, you can supply a default definition for getGradePointsFor
in BasicGradingStrategy that will be extended in HonorsGradingStrategy.
Move the definition for getGradePointsFor from RegularGradingStrategy to
BasicGradingStrategy. You should no longer declare BasicGradingStrategy as
abstract, since all of its methods supply definitions.
// BasicGradingStrategy.java
package sis.studentinfo;
// RegularGradingStrategy.java
package sis.studentinfo;
Extending
Methods public class RegularGradingStrategy extends BasicGradingStrategy {
}
In comparison to extending, Java will let you fully supplant a method’s de-
finition with a completely new one that has nothing to do with the original.
This is known as overriding a method. This is a semantic definition: The Java
compiler knows no distinction between overriding and extending; it’s all
overriding to the compiler. You, on the other hand, recognize an extending
method by its call to the superclass method of the same name.
Prefer extending to overriding.
When you use the super keyword, the Java VM looks in the superclass to
find the corresponding method definition.
Refactoring
The method basicGradePointsFor in BasicGradingStrategy is now superfluous. Refactoring
You can inline it and eliminate the method basicGradePointsFor.
package sis.studentinfo;
package studentinfo;
You can eliminate this class by changing the gradeStrategy reference in Stu-
dent to use BasicGradingStrategy as the default.
All the work you have done since learning about inheritance has been on
the code side only. You have not changed any tests. (I hope you have been
running your tests with each incremental change.) You have not changed
any behavior; you have only changed the way in which the existing behav-
ior is implemented. The tests in Student for GPA adequately cover the
behavior.
However, the tests in Student cover behavior within the context of manag-
ing students. You want to additionally provide tests at the unit level. The
general rule is to have at least one test class for each production class. You
should test each of HonorsGradingStrategy and BasicGradingStrategy indi-
vidually.
Here is a test for BasicGradingStrategy:
Refactoring
package sis.studentinfo;
import junit.framework.*;
import junit.framework.*;
Grade(int points) {
this.points = points;
}
int getPoints() {
return points;
}
}
...
210 INHERITANCE
You have now associated each named instance of the enum with a para-
meter. Further, you terminated the list of enum instances with a semicolon.
After that semicolon, code in the Grade enum declaration looks just like that
in any class type. The parameter associated with each named enum instance
is passed to the constructor of Grade. This points parameter is stored in an in-
stance variable named points. You retrieve the points via the getPoints method.
You can now simplify the code in BasicGradingStrategy to:
package sis.studentinfo;
import junit.framework.*;
import java.util.*;
import sis.studentinfo.*;
CALLING SUPERCLASS CONSTRUCTORS 211
CourseSession
SummerCourseSession
getEndDate(): Date
private SummerCourseSession(
String department,
String number,
212 INHERITANCE
Date startDate) {
super(department, number, startDate);
}
Date getEndDate() {
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(startDate);
int sessionLength = 8;
int daysInWeek = 7;
int daysFromFridayToMonday = 3;
int numberOfDays =
sessionLength * daysInWeek - daysFromFridayToMonday;
calendar.add(Calendar.DAY_OF_YEAR, numberOfDays);
return calendar.getTime();
}
}
After compiling, you should have two remaining errors that appear similar:
private CourseSession(
String department, String number, Date startDate) {
// ...
You made the constructor private in order to force clients to construct Course-
Session instances using the class creation method. The problem is, the private
access modifier restricts access to the constructor. Only code defined within
CourseSession itself can invoke its constructor.
Your intent is to expose the CourseSession constructor to code only in
CourseSession itself or code in any of its subclasses. Even if you define a
CourseSession subclass in a different package, it should have access to the Course-
Session constructor. You don’t want to expose the constructor to other
classes.
Specifying public access would allow too much access. Non-subclasses in
Calling
different packages would be able to directly construct CourseSession objects.
Superclass
Package access would not allow subclasses in different packages to access the Constructors
CourseSession constructor.
Java provides a fourth (and final) access modifier, protected. The protected key-
word indicates that a method or field may be accessed by the class in which it
is defined, by any other class in the same package, or by any of its subclasses.
A subclass has access to anything in its superclass marked as protected, even if
you define the subclass in a package other than the superclass.
Change the CourseSession constructor from private to protected:
protected CourseSession(
String department, String number, Date startDate) {
// ...
214 INHERITANCE
The compilation error regarding the constructor goes away. You have one re-
maining error: getEndDate references the private superclass field startDate directly.
startDate has private access in sis.studentinfo.CourseSession
calendar.setTime(startDate);
^
You could solve the problem by changing startDate to be a protected field. A bet-
ter solution is to require that subclasses, like any other class, access the field
by using a method.
Change the getStartDate method from package to protected access.
public class CourseSession implements Comparable<CourseSession> {
...
protected Date getStartDate() {
return startDate;
}
...
// SummerCourseSession.java:
Calling public Date getEndDate() {
Superclass GregorianCalendar calendar = new GregorianCalendar();
Constructors calendar.setTime(getStartDate());
int sessionLength = 8;
int daysInWeek = 7;
int daysFromFridayToMonday = 3;
int numberOfDays =
sessionLength * daysInWeek - daysFromFridayToMonday;
calendar.add(Calendar.DAY_OF_YEAR, numberOfDays);
return calendar.getTime();
}
Since I told you to be lazy and copy the code for getEndDate, note that you
had the additional effort of making the change in two places.
REFACTORING 215
Refactoring
Time to eliminate the duplicate code you hastily introduced.
The only change between getEndDate as defined in CourseSession and
SummerCourseSession is the session length. Define a protected method in
CourseSession to return this value:
When you override a method, you should precede it with an @Override anno-
tation. You use annotations to mark, or annotate, specific portions of code.
Other tools, such as compilers, IDEs, and testing tools, can read and inter-
pret these annotations. The Java compiler is the tool that reads the @Override
annotation.
The compiler ensures that for a method marked as @Override, a method with
the same name and arguments exists in a superclass. If you screwed up some-
how—perhaps you mistyped the method as getSessionLentgh instead of getSession-
Length—the compile fails. You see the message:
Java does not require you to add @Override annotations to your code. How-
ever, they provide valuable documentation for your code as well as protec-
tions from simple mistakes. You can read more about annotations in Lesson
15.
You may now remove getEndDate from SummerCourseSession. In Figure 6.3,
the getSessionLength operation appears twice, an indication that the subclass
Refactoring (SummerCourseSession) overrides the superclass (CourseSession) definition.
Note that I have preceded each method with access privilege information,
something I rarely do. Since normally I only display public information in a
UML sketch, I prefer to omit the + (plus sign) that indicates a publicly acces-
sible operation.
In this circumstance, however, getSessionLength is protected, not public. Adding
the access privilege indicators to the diagram in Figure 6.3 differentiates ac-
cess between the methods and highlights the relevancy of getSessionLength. The
UML indicator for protected is the pound sign (#).3
3
The UML access indicator for private is (-). The indicator for package-level access
is (~).
REFACTORING 217
CourseSession
+ getEndDate(): Date
# getSessionLength(): int
SummerCourseSession
# getSessionLength(): int
Idiosyncrasies
Since Java does not require you to add @Override annotations to methods, it’s easy
to forget. (That’s one thing a pair developer can be good for.) The @Override annota-
tion is a new J2SE 5.0 feature, so I have my excuse if I’ve forgotten to use it else-
where in Agile Java.
You’ll note that I do not use @Override on my setUp and tearDown methods, even
though the annotation is applicable for these TestCase method overrides. Using
@Override would be a good idea (particularly since it’s easy enough to spell setUp as
setup). But old habits are hard to break. I justify it by saying that it’s only test code
and the tests will help correct me anyway. Nonetheless, I recommend that you
avoid my bad habits.
Most of the logic for determining the end date of a session is fixed. The
Refactoring
only variance in the logic between CourseSession and SummerCourseSession
is the session length. This variance is represented by the abstraction of a sep-
arate method, getSessionLength. The CourseSession class can supply one imple-
mentation for getSessionLength, the SummerCourseSession another.
The method getEndDate in CourseSession acts as a template. It supplies the
bulk of the algorithm for calculating a session end date. Certain pieces of the
algorithm are “pluggable,” meaning that the details are provided elsewhere.
Subclasses can vary these details. The template algorithm in getEndDate is an ex-
ample of a design pattern known as Template Method.4
4
[Gamma1995].
218 INHERITANCE
More on Constructors
You call a superclass constructor from a subclass constructor by using the
super keyword, as demonstrated earlier. A call to a superclass constructor
must appear as the first line in a subclass constructor.
A subclass extends, or builds upon, a superclass. You can think of a sub-
class object as an outer layer, or shell, around an object of the base class. Be-
fore the subclass object can exist, Java must build and properly initialize an
object of the superclass. The following language test demonstrates how
classes are constructed, using a demo class called SuperClass and its subclass
SubClass.
// SuperClassTest.java
import junit.framework.TestCase;
// SuperClass.java
class SuperClass {
static boolean constructorWasCalled = false;
SuperClass() {
constructorWasCalled = true;
}
}
// SubClass.java
class SubClass extends SuperClass {
More on SubClass() {
Constructors }
}
The Java virtual machine requires every class to have at least one con-
structor. However, you can code a class without explicitly defining a con-
structor. If you don’t define a constructor Java will automatically generate a
default, no-argument constructor.
If you do not provide a super call in a subclass constructor, it is as if Java
inserted a call to the no-argument superclass constructor. Even constructors
that take parameters call the no-argument superclass constructor by default.
// SuperClassTest.java
import junit.framework.TestCase;
MORE ON CONSTRUCTORS 219
// SubClass.java
class SubClass extends SuperClass {
SubClass(String parm) {
}
}
SuperClass(String parm) {
constructorWasCalled = true;
}
}
// SubClass.java
class SubClass extends SuperClass {
SubClass(String parm) {
}
} More on
Constructors
The above code generates the following compiler error:
SubClass.java: cannot find symbol
symbol : constructor SuperClass()
location: class studentinfo.SuperClass
SubClass(String parm) {
^
Even though the session variable is of the type CourseSession, it still refers to a
SummerCourseSession in memory.
You defined the method getEndDate in both CourseSession and Summer-
CourseSession. When you send the getEndDate message using the session vari-
able, the message is received by a SummerCourseSession object. Java
executes the version of getEndDate defined in SummerCourseSession.
The client thinks it is sending a message to a CourseSession, but an object
of a class derived from CourseSession receives and interprets the message.
This is another example of polymorphism.
Session
CourseSession SummerCourseSession
to these unit tests. You will build these contracts using a pattern known as
Abstract Test.5
Technically, there is no compelling reason to make this refactoring. The
tests for CourseSession class could retain the contract for both CourseSession
and SummerCourseSession. Minor differences do exist, though. The Course-
Session class explicitly tracks the number of instances created; this is some-
thing the SummerCourseSession class need not do.
Building the Session superclass results in a slightly cleaner and easier to
understand hierarchy. Conceptually, a CourseSession seems to be an object at
the same hierarchical “level” as a SummerCourseSession.
It’s up to you to decide if such a refactoring is worthwhile. If it gives you a
simpler solution, go for it. If it creates artificial classes in the hierarchy with-
out any benefit, it’s a waste of time.
For the CourseSession example, you will refactor such that both Course-
Session and SummerCourseSession inherit from a common abstract super-
class, instead of SummerCourseSession inheriting from CourseSession. See
Figure 6.4.
Create an abstract test class, SessionTest, that specifies the base unit tests
The Principle of
for Session and its subclasses. Move testCreate, testComparable, and test- Subcontracting
EnrollStudents from CourseSessionTest into SessionTest. These tests represent
the contracts that should apply to all Session subclasses.
SessionTest contains an abstract factory method, createSession. Subclasses of
SessionTest will provide definitions for createSession that return an object of
the appropriate type (CourseSession or SummerCourseSession).
package sis.studentinfo;
import junit.framework.TestCase;
import java.util.*;
import static sis.studentinfo.DateUtil.createDate;
5
[George2002].
222 INHERITANCE
CourseSessionTest SummerCourseSessionTest
testCourseDates testEndDate
testCount
// SummerCourseSessionTest.java
package sis.summer;
import junit.framework.*;
import java.util.*;
import sis.studentinfo.*; The Principle of
Subcontracting
public class SummerCourseSessionTest extends SessionTest {
public void testEndDate() {
Date startDate = DateUtil.createDate(2003, 6, 9);
Session session = createSession("ENGL", "200", startDate);
Date eightWeeksOut = DateUtil.createDate(2003, 8, 1);
assertEquals(eightWeeksOut, session.getEndDate());
}
import junit.framework.TestCase;
import java.util.*;
import static sis.studentinfo.DateUtil.createDate;
package sis.studentinfo;
import java.util.*;
protected Session(
String department, String number, Date startDate) {
this.department = department;
this.number = number;
this.startDate = startDate;
}
int getNumberOfStudents() {
return students.size();
}
// CourseSession.java
package sis.studentinfo;
import java.util.*;
protected CourseSession(
String department, String number, Date startDate) {
super(department, number, startDate);
CourseSession.incrementCount();
}
// SummerCourseSession.java
package sis.summer;
import java.util.*;
import sis.studentinfo.*;
private SummerCourseSession(
String department,
String number,
Date startDate) {
super(department, number, startDate);
}
228 INHERITANCE
All that is left in the subclasses are constructors, static methods, and tem-
plate method overrides. Constructors are not inherited. Common methods,
such as enroll and getAllStudents, now appear only in the Session superclass. All
of the fields are common to both subclasses and thus now appear in Session.
Also important is that the return type for the create method in SummerCourse-
Session and CourseSession is the abstract supertype Session.
The test method testCourseDates appears in both SessionTest subclasses. You
could define a common assertion for this test in SessionTest, but what would
the assertion be? One simple assertion would be that the end date must be
later than the start date. Subclasses would strengthen that assertion by verify-
ing against a specific date.
Another solution would be to assert that the end date is exactly n weeks,
minus the appropriate number of weekend days, after the start date. The re-
sultant code would have you duplicate the logic from getEndDate directly in the
test itself.
Of perhaps more value is adding assertions against the method getSession-
Length in the abstract test:
Exercises
1. In the exercises for Lesson 5, you created a method to calculate the
strength of a piece based on its type and board position. Modify this
method to use a switch statement instead of an if statement.
2. Modify the strength calculation method to use a Map that associates piece
types with their base strength value. In order to place double values in Map,
you will need to declare the map as Map<Piece.Type,Double>.6
6
Lesson 7 includes a section on wrapper types that explains why this is so.
EXERCISES 229
otherwise you might get stuck in an infinite loop of the method calling
itself.
9. Your code to manage moves for kings and queens includes an if state-
ment. You can imagine that supporting further moves for pieces will
involve a bunch of conditional statements. One way of cleaning things
up is to create a Piece hierarchy, with separate subtypes for King,
Queen, Bishop, and so on.
In preparation for creating Piece subclasses, move the method
getPossibleMoves to the Piece class.
10. Now you are ready to create Piece subclasses. You will note that as
you create subclasses for Queen and King, the Piece.Type enum seems
redundant. In the next exercise, you’ll eliminate this enum. Create
Queen and King classes to extend Piece. Move the relevant tests from
PieceTest into QueenTest and KingTest. This will force you to move
code as well into the new subclasses. Also, create subclasses for the re-
maining pieces and alter the factory methods accordingly on Piece.
Don’t worry about coding up the move rules for the other pieces yet.
Refactor at will. Eliminate duplication. Add tests where tests were
missing due to moving methods onto a new class.
11. The final step is to eliminate the need for the Type enum. Since you
now have a subclass for each piece, you can move code depending on
the type into that sublcass.
You may need to test the type of the subclass created. Instead of
asking each piece for its Piece.Type value, you can simply ask for its
class:
piece.getClass()
Legacy Elements
In this lesson you will learn about legacy elements of Java. Java is a continu-
ally evolving language. The original release of Java was immature with
respect to both its language features and its class library. Over the years, Sun
has added to the language and class library in attempts to provide a more ro-
bust platform. Sun has removed little. The result is that Java contains many
features that Sun recommends you no longer use.
J2SE 5.0 introduces many new language elements, some that aspire to re-
place existing Java elements. As the most relevant example, the designers of
Java changed the means of iterating through a collection in 5.0. Previously,
the language supported iteration through a combination of the class library
and procedural looping constructs. Now, the Java language directly supports
iteration with the for-each loop.
In this lesson, you will learn about many Java elements that come from its
syntactical grandparent, the C language. These elements are largely proce-
dural in nature but are still necessary in order to provide a fully functional
programming language. Nonetheless, most of the time you should be able to
use constructs that are more object-oriented instead of the legacy elements in
this lesson.
While I downplay the use of these legacy elements, you must understand
them in order to fully master Java. They remain the underpinnings of the
Java language. In many circumstances, you will be forced to use them. And
in some cases, they still provide the best solution to the problem at hand.
Some of the legacy elements you will learn about include:
Legacy
• for, while, and do loops Elements
231
232 LEGACY ELEMENTS
• wrapper classes
• arrays
• varargs
Each of these elements, with the exception of varargs, has been around
since the advent of Java. The concept of collections, Iterators, and wrapper
classes are object-oriented constructs. Everything else derives from the C lan-
guage. The switch statement that you learned in Lesson 6 is also a legacy ele-
ment that was derived from C.
Looping Constructs
The for loop as you have learned it provides a means of iterating through a
collection and operating on each element.
You will need more general-purpose looping. Perhaps you need to execute
a body of code infinitely, loop until some condition is met, or send a message
ten times. Java meets these needs with three looping constructs: while, do, and
for.
The modified Student constructor code shows your intent of how to solve the
problem:
public Student(String fullName) {
this.name = fullName;
credits = 0;
List<String> nameParts = split(fullName);
setName(nameParts);
} Breaking Up a
Student’s Name
Programming by intention is a useful technique that helps you figure out
what you need to do before you figure out how you are going to do it.1 It can
be viewed as decomposing the problem into smaller abstractions. You can
put these abstractions into code before actually implementing the abstrac-
tions. Here, you know that you need to split the full name into different
parts, but you don’t yet know how.
1
[Astels2003], p. 45.
234 LEGACY ELEMENTS
Once you have figured out how to split the string into a list of up to three
name parts, the implementation of setName is straightforward. It involves a few
if-else statements. Each if conditional tests the number of name parts avail-
able in the list of tokens:
private void setName(List<String> nameParts) {
if (nameParts.size() == 1)
this.lastName = nameParts.get(0);
else if (nameParts.size() == 2) {
this.firstName = nameParts.get(0);
this.lastName = nameParts.get(1);
}
else if (nameParts.size() == 3) {
this.firstName = nameParts.get(0);
this.middleName = nameParts.get(1);
this.lastName = nameParts.get(2);
}
}
The setName code always sets a value into lastName. However, it does not al-
ways set the value of firstName or middleName. You will need to ensure that these
fields are properly initialized in Student:
private String firstName = "";
private String middleName = "";
private String lastName;
char ch = string.charAt(index);
if (ch != ' ') // prefer Character.isSpace. Defined yet?
word.append(ch);
else
if (word.length() > 0) {
results.add(word.toString());
word = new StringBuffer();
}
index++;
}
if (word.length() > 0)
results.add(word.toString());
return results;
}
You use the while loop to execute a statement or block of code as long as a
condition holds true.
In this example, after Java initializes an index counter to 0, it executes the
while loop. As long as the index is less than the string length (index < string
.length()), Java executes the body of the while loop (all code between the braces
following the while conditional).
The body in this example extracts the character at the current index from
the string. It tests whether the character is a space (‘ ‘) or not. If not, the
character is appended to the current StringBuffer instance. If so, and if there
are any characters in the current StringBuffer, the contents of the StringBuffer
represent a complete word and are added to the results. A new StringBuffer,
to represent a new word, is created and assigned to the word reference.
Each time the body of the while loop completes, control is transferred back
up to the while loop conditional. Once the conditional returns a false result,
the while loop terminates. Control is then transferred to the statement imme-
diately following the while loop body. In the example, the statement immedi-
ately following is an if statement that ensures that the last word extracted
becomes part of the results.
The modified test should pass.
Refactoring Breaking Up a
Student’s Name
The setName method is a bit repetitive. You can refactor it by taking advantage
of having the name parts in a list that you can manipulate.
private void setName(List<String> nameParts) {
this.lastName = removeLast(nameParts);
String name = removeLast(nameParts);
if (nameParts.isEmpty())
this.firstName = name;
else {
236 LEGACY ELEMENTS
this.middleName = name;
this.firstName = removeLast(nameParts);
}
}
You actually create more lines of code overall with this solution. But you
eliminate the duplication and hard-coded indexes rampant in setName. Further,
you produce a generic method, removeLast, that later may be a useful abstrac-
tion.
else
if (word.length() > 0) {
results.add(word.toString());
word = new StringBuffer();
}
}
if (word.length() > 0)
results.add(word.toString());
return results;
}
In the for loop example, the initialization code declares index as an int and
initializes it to 0. The conditional tests that index is less than the number of
characters in the String argument. As long as the condition holds true, Java
executes the body of the loop. Subsequent to each time Java executes the
loop, Java increments index (index++); it then transfers control back to the con-
ditional. Once the conditional returns false, Java transfers control to the
statement following the for loop body.
In the example, Java executes the body of the for loop once for each char-
acter in the input string. The index value ranges from zero up to the number
of characters in the input string minus one.
The use of the letter i as a for loop index variable is an extremely common Breaking Up a
Student’s Name
idiom. It is one of the few commonly accepted standards that allows you to
abbreviate a variable name. Most developers prefer to use i over a full word
such as index.
2
This method and several other methods in this lesson such as the upcoming method
isPalindrome, have nothing to do with the student information system. They are here to
help demonstrate some of the lesser-seen nuances of Java syntax.
238 LEGACY ELEMENTS
// tests
public void testPalindrome() {
assertFalse(isPalindrome("abcdef"));
assertFalse(isPalindrome("abccda"));
assertTrue(isPalindrome("abccba"));
assertFalse(isPalindrome("abcxba"));
assertTrue(isPalindrome("a"));
assertTrue(isPalindrome("aa"));
assertFalse(isPalindrome("ab"));
assertTrue(isPalindrome(""));
assertTrue(isPalindrome("aaa"));
assertTrue(isPalindrome("aba"));
assertTrue(isPalindrome("abbba"));
assertTrue(isPalindrome("abba"));
assertFalse(isPalindrome("abbaa"));
assertFalse(isPalindrome("abcda"));
}
A local variable that you declare in the initialization code of a for loop is
valid only for the scope of the for loop. Thus, in the countChars method, you
must declare count prior to the loop, since you return count after the for loop.
Java stipulates that “if you have a local variable declaration, each part of
the expression after a comma is expected to be a part of that local variable
declaration.”3 So while it would be nice to be able to recode countChars to de-
clare i and only initialize count, Java expects the following initialization code
Breaking Up a
Student’s Name
to declare both i and count.
// this code will not compile!
public static int countChars(String input, char ch) {
int count;
for (int i = 0, count = 0; i < input.length(); i++)
if (input.charAt(i) == ch)
count++;
return count;
}
3
[Arnold2000].
BREAKING UP A STUDENT’S NAME 239
But since you already declared count before the for loop, the Java compiler
rejects the attempt:
count is already defined in countChars(java.lang.String,char)
As mentioned, the most common use for for loops is to count from 0 up to
n – 1, where n is the number of elements in a collection. Nothing prohibits
you from creating for loops that work differently. The isPalindrome method
shows how you can count upward at the same time as counting downward.
Another language test shows how you can have the update expression do
something other than decrement or increment the index:
public void testForSkip() {
StringBuilder builder = new StringBuilder();
String string = "123456";
for (int i = 0; i < string.length(); i += 2)
builder.append(string.charAt(i));
assertEquals("135", builder.toString());
}
The do Loop
The do loop works like a while loop, except that the conditional to be tested
appears at the end of the loop body. You use a do loop to ensure that the you
Breaking Up a
execute the body of a loop at least once.
Student’s Name
In the thirteenth century, Leonardo Fibonacci came up with a numeric se-
quence to represent how rabbits multiply.4 The Fibonacci sequence starts
with 0 and 1. Each subsequent number in the sequence is the sum of the two
preceding numbers in the sequence. The test and code below demonstrate an
implementation of the Fibonacci function using a do loop.
4
[Wikipedia2004]
240 LEGACY ELEMENTS
sequence = "8";
assertEquals(sequence, sequenceUsingDo(8, 8));
assertEquals(sequence, sequenceUsingFor(8, 8));
assertEquals(sequence, sequenceUsingWhile(8, 8));
}
do {
if (i > start)
builder.append(',');
builder.append(i);
} while (++i <= stop);
return builder.toString();
}
As this example shows, you will find that one of the loop variants is usu-
ally more appropriate than the other two. Here, a for loop is best suited since
the loop is centered around counting needs.
If you have no need to maintain a counter or other incrementing variable,
the while loop will usually suffice. Most of the time, you want to test a condi-
tion each and every time upon entry to a loop. You will use the do loop far
less frequently. The need to always execute the loop at least once before test-
ing the condition is less common.
Refactoring
Refactoring
isPalindrome
The isPalindrome method expends almost twice as much effort as necessary to
determine whether a string is a palindrome. While normally you shouldn’t
worry about performance until it is a problem, an algorithm should be clean.
242 LEGACY ELEMENTS
Be cautious when using recursion! Make sure you have a way of “break-
ing” the recursion, otherwise your method will recurse infinitely. You can
often use a guard clause for this purpose, as in the fib method (which has two
guard clauses).
Looping Control
Statements
session.enroll(createFullTimeStudent());
5
It actually removes whitespace, which includes space characters as well as tab, new
line, form feed, and carriage return characters.
244 LEGACY ELEMENTS
You can easily rewrite this (and most) uses of continue using if-else statements.
Sometimes the use of continue provides the more-elegant, easy-to-read presen-
tation.
table.add(row1);
table.add(row2);
assertTrue(found(table, "3"));
THE TERNARY OPERATOR 245
assertFalse(found(table, "8"));
}
If you did not associate the search label with the break statement in this ex-
ample, Java would break out of only the innermost for loop (the one with the
expression Integer num: row). Instead, the break statement causes looping to termi-
nate from the search label, representing the outermost loop in this example.
The labeled continue statement works similarly, except the Java VM trans-
fers control to the loop with the label instead of breaking from the loop with
the label.
There are few situations where your solution will require labeled break or
continue statements. In most cases, you can easily restructure code to eliminate
them, either by use of if-else statements or, even better, by decomposition of
methods into smaller, more composed methods. Use the labeled statements
only when the solution is easier to comprehend.
String message =
"the course has " + getText(sessions) + " sessions";
...
private String getText(int sessions) {
if (sessions == 1)
return "one";
return "many";
}
246 LEGACY ELEMENTS
For simple conditional expressions that must return a new value, Java pro-
vides a shortcut form known as the ternary operator. The ternary operator
compacts the if-else statement into a single expression. The general form of
the ternary operator is:
conditional ? true-value : false-value
If conditional returns the value true, the result of the entire expression is true-
value. Otherwise the result of the entire expression is false-value. Using the
ternary operator, you can rewrite the previous code to produce a message as:
String message =
"the course has " + (sessions == 1 ? "one" : "many") + " sessions";
Legacy Collections
Prior to the SDK version 1.2, there was no collections “framework” in Java.
Java contained two main workhorse collections—java.util.Vector and
java.util.Hashtable6—that still exist today. The analogous classes in modern
Java are respectively java.util.ArrayList and java.util.HashMap. The class
java.util.HashMap provides similar functionality as the class java.util.Enum
Map, with which you are already familiar. Refer to Lesson 9 for more infor-
mation on HashMap.
Legacy Both Vector and Hashtable are concrete; they implement no common in-
Collections
terfaces. They incorporate support for multithreaded programming7 by de-
fault, a choice that often results in unnecessary overhead. Unfortunately,
today you will still encounter a large amount of code that uses or requires
use of the Vector class.
6
There were only two other collection classes: BitSit and Stack (a subclass of Vector).
Both were minimally useful.
7
See Lesson 13 for a discussion of multithreaded programming.
ITERATORS 247
Since there were no corresponding interfaces for these classes in the initial
versions of Java, you were forced to assign Vector or Hashtable instances to a
reference to an implementation class:
Vector names = new Vector();
Sun also retrofitted Vector and Hashtable so that they are parameterized
types. You can thus code:
List<String> names = new Vector<String>();
Map<Student.Grade,String> dictionary =
new Hashtable<Student.Grade,String>();
Iterators
You have learned to use the for-each loop to iterate through each element in a
collection. This form of the for loop is a new construct, introduced in
J2SE 5.0.
Prior to the for-each loop, the preferred way to iterate through a collection
was to ask it for a java.util.Iterator object. An Iterator object maintains an
internal pointer to an element in the collection. You can ask an iterator to re-
turn the next available element in the collection. After returning an element
from the collection, an Iterator advances its internal pointer to refer to the
next available element. You can also ask an iterator whether or not there are
more elements in a collection. Iterators
As an example, rewrite the averageGpaForPartTimeStudents method shown earlier
in this lesson to use an Iterator object and a for loop instead of a for-each loop:
double averageGpaForPartTimeStudents() {
double total = 0.0;
int count = 0;
if (student.isFullTime())
continue;
count++;
total += student.getGpa();
}
if (count == 0) return 0.0;
return total / count;
}
You usually obtain an Iterator by sending the message iterator to the collec-
tion. You assign the Iterator object to an Iterator reference that you bind to
the type of object stored within the collection. Here, you bind the Iterator to
the type Student, which is what the students collection stores.
The Iterator interface defines three methods: hasNext, next, and remove (which
is optional and infrequently needed). The hasNext method returns true if there
are any elements remaining in the collection that have not yet been returned.
The next method returns the next available element from the collection and
advances the internal pointer.
The legacy collections Vector and Hashtable use an analogous technique
known as enumeration. The solution is virtually the same, only the names are
different. Redefine the students instance variable in Session to be a Vector.
package sis.studentinfo;
import java.util.*;
double averageGpaForPartTimeStudents() {
double total = 0.0;
int count = 0;
Instead of asking for an Iterator using the iterator method, you ask for an
Enumeration by using the elements method. You test whether there are more
elements available using hasMoreElements instead of hasNext. You retrieve the next
element using nextElement instead of next. Sun introduced the Iterator in re-
sponse to many complaints about the unnecessary verbosity of the class name
Enumeration and its method names.
Since Vector implements the List interface, you can send a Vector the itera-
tor message to obtain an Iterator instead of an Enumeration.
Revert your Session code to use modern collections and the for-each loop.
assertEquals(session.getAllStudents(), results);
}
When you compile, you will receive an error, since Session does not yet sup-
port the ability to be iterated over.
You must change the Session class to implement the Iterable interface.
Since you want the for-each loop to recognize that it is iterating over a
collection containing only Student types, you must bind the Iterable interface
to the Student type.
abstract public class Session
implements Comparable<Session>, Iterable<Student> {
// ...
Now you only need to implement the iterator method in Session. Since Ses-
sion merely encapsulates an ArrayList of students, there is no reason that the
iterator method can’t simply return the ArrayList’s Iterator object.
Casting
The need for casting of references occurs when you, the programmer, know
that an object is of a certain type but the compiler couldn’t possibly know
what the type is. Casting allows you to tell the compiler that it can safely as-
sign an object of one type to a reference of a different type.
Sun’s introduction of parameterized types removed much of the need for
casting in Java. Prior to J2SE 5.0, since the collection classes in Java did not
support parameterized types, everything that went into a collection had to go
in as an Object reference, as shown by the signature for the add method in the
List interface:
public boolean add(Object o)
Note the absence of any spaces between the cast and the target. While you
may interject spaces between a cast and its target, the more commonly ac-
cepted style specifies no spaces.
It is important to understand that casting does nothing to change the tar-
get object. The cast to Student type only creates a new reference to the same
memory location. Since this new reference is of type Student, the Java com-
piler will then allow you to send Student-specific messages to the object via
the reference. Without casting, you would only be able to send messages de-
fined in Object to the Student object.
Using parameterized types, there are few reasons to cast object references.
If you find yourself casting, determine if there is a way to avoid casting by
using parameterized types.
As an exercise, the following test demonstrates the “old way” of iterating
through a collection—without a for-each loop and without parameterized
types. Note that this code will still work under J2SE 5.0, although you may
receive warnings because you are not using the parameterized collection
types.
public void testCasting() {
List students = new ArrayList();
students.add(new Student("a"));
students.add(new Student("b"));
Iterator it = students.iterator();
while (it.hasNext()) {
Student student = (Student)it.next();
names.add(student.getLastName());
}
assertEquals("a", names.get(0));
assertEquals("b", names.get(1)); Casting
}
Wrapper Classes
The student system must store student charges in a collection to be
totaled later. Each charge is an int that represents the number of
cents a student spent on an item.
Remember that primitive types are not objects—they do not inherit from
the class java.lang.Object. The java.util.List interface only supplies an add
method that takes a reference type as a argument. It does not supply over-
loaded add methods for each of the primitive types.
In order to store the int charge in a collection, then, you must convert it to
an object. You accomplish this by storing the int in a wrapper object.
For each primitive type, java.lang defines a corresponding wrapper class.8
Type Wrapper Class
char Character
byte Byte
short Short
int Integer
long Long
float Float
double Double
boolean Boolean
Each wrapper class provides a constructor that takes a primitive of the ap-
propriate type as argument. The wrapper stores this primitive and allows for
extracting it using a corresponding getter method. In the case of the Integer
wrapper, you extract the original int by sending the message intValue to the
wrapper.
A test (in StudentTest) for the example:
Wrapper
Classes public void testCharges() {
Student student = new Student("a");
student.addCharge(500);
student.addCharge(200);
student.addCharge(399);
assertEquals(1099, student.totalCharges());
}
8
The table lists all available primitive types and corresponding wrappers. You will
learn more about the unfamiliar types in Lesson 10.
WRAPPER CLASSES 253
If you had to write the implementation using pre-J2SE 5.0 code, it would
look something like this:
The addCharge method shows how you would wrap the int in an instance of
the Integer wrapper class in order to pass it to the add method of the charges
collection. When iterating through the collection (in totalCharges), you would
have to cast each retrieved Object to the Integer wrapper class. Only then
would you be able to extract the original int value through use of the intValue
method.
J2SE 5.0 simplifies the code through the use of parameterized types and
the for-each loop.
classes, these methods would have nowhere to go. You have already used the
Character class method isWhitespace.
A further simplification of the code comes about from one of the new
J2SE 5.0 features, autoboxing.
then any of the following message sends will resolve to this add method:
The second add message send works because ArrayList is a subclass of List.
If Java finds no direct signature match, it attempts to find a signature
match based on wrapping. Any method with an Object argument in the ap-
propriate place is a match.
In the addCharge method, the explicit wrapping of the int into an Integer is no
longer necessary:
Arrays
I have mentioned arrays several times in this book. An array is a fixed-size,
contiguous set of slots in memory. Unlike an ArrayList or other collection ob- Arrays
ject, an array can fill up, preventing you from adding more elements. The
only way to add elements to a full array is to create a second larger array and
copy all elements from the first array to the second array.
Java provides special syntactical support for arrays. Indirectly, you have
been using arrays all along, in the form of the class ArrayList. An ArrayList
stores all elements you add to it in a Java array. The ArrayList class also en-
capsulates the tedium of having to grow the array when you add too many
elements.
256 LEGACY ELEMENTS
import junit.framework.*;
assertEquals(92, performance.get(1));
You store the scores for the tests in an int array named tests:
private int[] tests;
ARRAYS 257
This declaration only says that the instance variable tests is of type int[]
(you read this as “int array”). The braces are the indicator that this is an
array. You use the braces to subscript, or index, the array. By subscripting the
array, you tell Java the index of the element with which you wish to work.
Java allows you to declare an array with the braces following the variable.
private int tests[]; // don’t declare arrays this way
You allocate an array using the new keyword. After the new keyword, you spec-
ify the type (int in this example) that the array will hold and the number of
slots to allocate (numberOfTests).
Once you initialize an array, you can set a value into any of its slots. The
type of the value must match the type of the array. For example, you can
only set int values into an int[].
public void set(int testNumber, int score) {
tests[testNumber] = score;
}
The line of code in set assigns the value of score to the slot in the tests array
that corresponds to the testNumber. Indexing of arrays is 0-based. The first slot
is slot #0, the second slot #1, and so on.
The get method demonstrates accessing an element in an array at a particu-
lar index:
public int get(int testNumber) {
return tests[testNumber];
Arrays
}
You can iterate an array like any other collection by using a for-each loop.
public double average() {
double total = 0.0;
for (int score: tests)
9
Specifically, a NullPointerException. See Lesson 8 for more information on excep-
tions.
258 LEGACY ELEMENTS
total += score;
return total / tests.length;
}
The average method also shows how you may access the number of ele-
ments in an array by using a special variable named length.
You can iterate an array using a classic for loop:
You may declare an array as any type, reference or primitive. You might have
an instance variable representing an array of Students:
Array Initialization
When you allocate an array, Java initializes its elements to the default value
for the type. Java initializes all slots in an array of numerics to 0, to false in an
array of booleans, and to null in an array of references.
Java provides special array initialization syntax that you can use at the
time of array instantiation and/or declaration.
The setScores method passes each of the four score arguments into an array
initializer.
Arrays public void setScores(int score1, int score2, int score3, int score4) {
tests = new int[] { score1, score2, score3, score4 };
}
The array initializer in this example produces an array with four slots,
each populated with an int value.
A shortcut that you can use as part of an array declaration assignment
statement is:
int[] values = { 1, 2, 3 };
ARRAYS 259
You may terminate the list of values in an array initializer with a comma.
The Java compiler ignores the final comma (it does not create an additional
slot in the array). For example, the preceding declaration and initialization is
equivalent to:
int[] values = {
1,
2,
3,
};
assertEquals(3, values.length);
Each slot requires four bytes, since it takes four bytes to represent an int.10
The result is the address 0x108, where the element 55 can be found.
10
Java stores primitive types directly in the array. An array of objects would store a
contiguous list of references to other locations in memory.
260 LEGACY ELEMENTS
memory
address
0 x 0000
int[] x = { 47, 84, 55 };
0 x 0100
47
0 x 0104
84
0 x 0108
55
0 x 0112
Varargs
If you want to pass a variable number of similar-typed arguments to a
method, you must first combine them into a collection. The setScores method
above is restricted to four test scores; you would like to be able to pass any
number of scores into a Performance object. Prior to J2SE 5.0, a standard
idiom for doing so was to declare a new array on the fly:
Arrays public void testArrayParm() {
Performance performance = new Performance();
performance.setScores(new int[] { 75, 72, 90, 60 });
assertEquals(74.25, performance.average(), tolerance);
}
Method calls to setScores can now have a variable number of test scores:
public void testVariableMethodParms() {
Performance performance = new Performance();
performance.setScores(75, 72, 90, 60);
assertEquals(74.25, performance.average(), tolerance);
performance.setScores(100, 90);
assertEquals(95.0, performance.average(), tolerance);
}
Multidimensional Arrays
Java also supports arrays of arrays, also known as multidimensional arrays.
A classic use of multidimensional arrays is to represent matrices. You can cre-
ate arrays that have up to 255 dimensions, but you will probably never need Arrays
more than 3 dimensions. The following language test demonstrates how you
allocate, populate, and access a two-dimensional array.
// 0 1 2 3
// 4 5 6 7
// 8 9 10 11
public void testTwoDimensionalArrays() {
final int rows = 3;
final int cols = 4;
int count = 0;
262 LEGACY ELEMENTS
You do not need to allocate all dimensions at once. You can allocate di-
mensions starting at the left, leaving the rightmost dimensions unspecified.
Later, you can allocate these rightmost dimensions. In testPartialDimensions, the
code initially allocates matrix as an int array of three rows. Subsequently, it al-
locates each slot in the rows portion of the array to differing-sized int arrays
of columns.
// 0
// 1 2
// 3 4 5
public void testPartialDimensions() {
final int rows = 3;
int[][] matrix = new int[rows][];
matrix[0] = new int[]{ 0 };
matrix[1] = new int[]{ 1, 2 };
matrix[2] = new int[]{ 3, 4, 5 };
assertEquals(1, matrix[1][0]);
assertEquals(5, matrix[2][2]);
}
• sort it (sort)
• convert it to an object that implements the List interface (asList)
• compare it to another single-dimensional array of the same type (equals)
• obtain a hash code (see Lesson 9) (hashCode)
• fill each of its elements, or a subrange of its elements, with a specified
value (fill)
• obtain a printable representation of the array (toString)
I’ll discuss the equals method in more depth. For more information about
the other methods, refer to the Java API documentation for more informa-
tion on java.util.Arrays.
Arrays.equals
An array is a reference type, regardless of whether you declare it to contain
primitives or references. If you create two separate arrays, the Java VM will
store them in separate memory locations. This means that comparing the two
arrays with == will result in false.
Since an array is a reference, you can compare two arrays using the equals
method. But even if you allocate both arrays to exactly same dimensions and
populate them with exactly the same contents, the comparison will return
false.
You can use the Arrays.equals method to compare the contents, not the mem-
ory location, of two arrays.
Refactoring
Splitting a String
While either the hand-coded while loop or for loop in the Student method split
will work, the resulting code is fairly complex. Java provides at least two
more ways to break apart String objects into tokens. First, you will learn
how to use the StringTokenizer class to break an input string into individual
components, or tokens. The StringTokenizer class is considered a legacy
class, meaning that Sun wants you to supplant its use with something better.
That something better is the split method, defined on String. Once you have
coded the solution using StringTokenizer, you will recode it to use String.split.
Up until Java 1.4, Sun recommended the StringTokenizer as the solution
for tokenizing strings. You will still encounter a lot of code that uses String-
Tokenizer; that’s why you’ll learn it here. In Java 1.4, Sun introduced the reg-
ular expressions API. Regular expressions are specifications for
pattern-matching against text. A robust, standardized language defines the
patterns you can express with regular expressions. The newer String.split
method uses a regular expression, making it far more effective than the
StringTokenizer solution. For more information about the regular expres-
sions API, see Additional Lesson III.
The constructor for the class java.util.StringTokenizer takes as parameters
an input String and a String representing a list of character delimiters. String-
Tokenizer then breaks the input String into tokens, allowing you to iterate
through each token in turn. The while loop provides the best mechanism for
traversing these tokens:
Part of your goal should be to make changes with as little impact to other
code as possible. The single line of code that now appears in your split
method seems like it should do the trick:
The split method takes a single parameter, the list of characters that repre-
sent word boundaries. It returns an array of words. The Arrays class method
asList takes an array and returns a List representation of that array.
One would think that this line of code would be a suitable replacement for
the previous code that constructed the list using StringTokenizer. But it
doesn’t work. You will receive many JUnit errors indicating that the remove call
266 LEGACY ELEMENTS
The split method uses a Java feature known as regular expressions. A reg-
ular expression is a set of syntactical elements and symbols that is used to
match text. You have likely used a simple form of regular expression when
listing files in a directory. You use the asterisk (*) as a wildcard symbol to
match any sequence of characters in a filename. See Additional Lesson III for
an overview of the powerful regular expressions feature.
Exercises
1. To see the different loop types in action, code the factorial algorithm
(without using recursion). The definition of factorial:
if n is 0, n factorial = 1.
if n is 1, n factorial = 1.
Exercises if n is greater than one, n factorial is 1 * 2 * 3 * ... up to n
Code the tests, then create a factorial method using a while loop. Next,
modify the same method to pass the tests without using the while key-
word, but instead using the C-style for loop. Finally, recode it again
using the do-while loop. Notice the changes you must make in order to
use the different loop structures. Also note that you only need one
loop structure to solve any looping problem.
a) Why would you prefer one loop structure over the others?
b) Which is shortest?
EXERCISES 267
and get the test to pass using the break keyword to end the loop.
2. Demonstrate your understanding of the continue keyword. Create a
method to return a string containing the numbers from 1 to n. Sepa-
rate each pair of numbers with a single space. Append an asterisk to
the numbers divisible by five. For example, setting n to 12 would re-
sult in the string:
"1 2 3 4 5* 6 7 8 9 10* 11 12".
3. a) Break the results of the string from the previous exercise into a
Vector of substrings. Split the string on the space character. The
string "1 2 3 4 5* 6 7" would split into a Vector containing the strings
"1", "2", "3", "4", "5*", "6", and "7".
b) Iterate through the elements of the vector using an Enumeration
and recreate the string from Exercise #2.
c) Remove all parameterized type information, note the compiler er-
rors, and correct the code where necessary.
4. In an earlier exercise, you wrote a test to ensure that the list of valid
moves for a given piece contained a set of squares. Simplify this asser-
tion to something like:
public void testKingMoveNotOnEdge() {
Piece piece = Piece.createBlackKing();
board.put("d3", piece);
assertContains(piece.getPossibleMoves("d3", board),
"c4", "d4", "e4", "c3", "e3", "c2", "d2", "e2");
}
wanted to iterate through it. But the best solution is to not require the
client to do the iteration themselves. One sophisticated (and some-
what complex) solution to accomplish this involves the visitor design
pattern. See the book Design Patterns11 for further information. For
now, exposing the board’s underlying two-dimensional array will
suffice.
Compare and contrast the two Board versions. Which is easier to
read and understand? Which involves the least code?
6. Make the Board class iterable, so that you can access the pieces (not
including “no piece” objects) using a for-each loop. A complex solution
would involve creating a separate Iterator class that tracks both a
rank and file index. A simpler solution would involve looping through
the board’s matrix and adding each piece to a List object. You could
then return the iterator from the List object.
Once you have made the class iterable, go back and change any
code that loops through all pieces to use a for-each loop. Look for op-
portunities to refactor code so that you use the for-each loop as much
as possible. For example, if you haven’t already, modify your code so
that each Piece object stores its current rank and file.
7. Read the javadoc API for the ArrayList class and determine a way to
simplify the last example in the chapter—the one that splits the stu-
dent name and returns a List of name parts. It is possible to recode the
split method without the use of a loop at all! Hint: Look at the con-
structors for ArrayList.
Exercises
11
[Gamma1995].
Lesson 8
In this lesson you will learn about exceptions and logging. Exceptions in Java
are a transfer control mechanism. You generate exceptions to signal problem-
atic conditions in your code. You write code to either acknowledge or handle
exceptions that are generated.
Java provides a flexible logging facility that allows you to record informa-
tion as your application executes. You might choose to log exceptions, inter-
esting occurrences, or events that you want to track.
Things you will learn include:
• the try-catch block
• checked vs. unchecked (runtime) exceptions
• errors vs. exceptions
• the throws clause
• the exception hierarchy
• creating your own exception type
• exception messages
• working with multiple exceptions
• rethrowing exceptions
• working with the stack trace
• the finally block
• the Formatter classes
• the Java logging API Exceptions and
Logging
• logging to files
• logging handlers
269
270 EXCEPTIONS AND LOGGING
• logging properties
• logging hierarchies
Exceptions
In Lesson 4, you saw how your code generated a NullPointerException when
you did something with a variable that was not properly initialized.
Exceptions are objects that represent exceptional conditions in code. You
can create an Exception object and throw it. If you are aware that there
might be exceptions, you can write code that explicitly deals with, or catches,
them. Or, you can declare that your code chooses to not deal with excep-
tions, and let someone else handle the problem.
A thrown exception represents a transfer of control. You may throw an
exception at any point in code; other APIs may similarly throw exceptions at
any time. The VM may also throw exceptions. From the point the exception
is thrown, the Java VM transfers control to the first place that deals with, or
catches, the exception. If no code catches the exception, the result is abnor-
mal program termination.1
You will ultimately need to build a user interface that allows for
entry of test scores for students in a session. The user interface is
where anything can go wrong: a user can type a number that is too
large, they can type nothing, or they can type garbage. Your job will
be to deal with invalid input as soon as it is typed in.
Test scores typed into the user interface come in as String objects. You will
need to convert these strings into ints. To do so, you can use the Integer
wrapper class utility method named parseInt. The parseInt method takes a
String as a number and attempts to convert it to a number. If successful,
parseInt returns the appropriate int. If the source String contains invalid input,
the code in parseInt throws a NumberFormatException.
Start with a simple test representing a successful case:
package sis.studentinfo;
import junit.framework.TestCase;
package sis.studentinfo;
Then write a second test that shows what happens when invalid input is
passed to the score method:
This isn’t the complete test, but it will demonstrate what happens when
code throws an exception. Compile the code and run the tests. JUnit will re-
port an error instead of a test failure. An error means that code within the
test (or, of course, code that the test called) generated an exception that was
not handled. The exception’s stack trace appears in the details window in
JUnit, showing that the Integer class generated a NumberFormatException.
You want your test to demonstrate that the score method generates an ex-
ception when clients pass invalid input to it. The test case documents a case
for which you expect to get an exception. Thus, you want the test to pass if
the exception occurs and fail if it does not.
Java provides a construct called a try-catch block that you use to trap ex-
ceptions. The standard form of a try-catch block contains two blocks of code.
The try block consists of code that might throw an exception. The catch block
contains code to execute if an exception is generated.
The code in testBadScoreEntered presents the most common idiom used when
testing for exceptions. The try block wraps the score message send, since it is
the code that could generate the NumberFormatException. If the code in score
generates an exception, the Java VM immediately transfers control from the
point the exception was thrown to the catch block.
272 EXCEPTIONS AND LOGGING
If execution of the code in score does not raise an exception, control pro-
ceeds normally and Java executes the next statement in the try block. In
testBadScoreEntered, the next statement is a call to the JUnit method fail. Scorer-
Test inherits fail (indirectly) from junit.framework.TestCase. Execution of
fail immediately halts the test method; the VM executes no more of its code.
JUnit reports the test method as a failure. Calling fail is equivalent to calling
assertTrue(false) (or assertFalse(true)).
You expect score to generate an exception. If it does not, something has
gone wrong, and you want to fail the test. If score does generate an exception,
the fail statement is skipped, since the VM transfers control to the catch block.
The catch block is empty. Tests are about the only place where your catch
blocks should ever be empty. Normally, you want to deal with a trapped ex-
ception somehow. You would do this in the catch block. In this lesson, I will
present you with options for managing a caught exception.
In testBadScoreEntered, receiving an exception is a good thing. You document
that by naming the NumberFormatException object in the catch clause
success. Most code you will encounter will use a generic name for the excep-
tion object, such as e. Nothing says that you can’t improve on this. Use an ex-
ception name to describe why you expect an exception.
The “exception test” represents what other client code will have to deal
with. Somewhere in the user interface code, you will have code that you
structure very similar to the code in testBadScoreEntered. The test documents the
potential for score to generate a NumberFormatException and under what
circumstance that potential exists.
assertTrue(scorer.isValid("75"));
assertFalse(scorer.isValid("bd"));
}
// Scorer.java
public boolean isValid(String input) {
try {
Integer.parseInt(input);
return true;
}
catch (NumberFormatException e) {
return false;
}
}
A client first calls isValid. If isValid returns true, the client could safely call
the score method. If isValid returns false, the client could apprise the user of the
invalid input.
One benefit of using this construct is that the client would not have to
code a try-catch block. You want to use exceptions as little as possible to con-
trol the flow of your code. By catching an exception as close to the source as
possible, you eliminate the need for clients to use try-catch blocks to manage
their control flow.
Another possibility would be for the score method to have a try-catch block.
When it caught a NumberFormatException, it would return -1 or some other
special int value that the client could recognize.
Checked Exceptions
Each course session at the university will have its own web page.
After creating a Session object, you can send it a String representing
the URL2 of its web page. Session should create a java.net.URL ob-
ject using this string. One of the constructors of java.net.URL takes a prop-
erly formed URL string. To be properly formed, a URL must follow a
number of rules for its component parts, such as the protocol name and host
name. If you create a java.net.URL with an improperly formed URL, the con-
structor of java.net.URL throws an exception of type java.net.Malformed-
URLException.
Checked
A simple test in SessionTest sets the URL into the session as a string. The Exceptinos
test then retrieves the URL as a java.net.URL object. Finally, the test ensures
2
Uniform Resource Locator, a pointer to a resource on the World Wide Web.
274 EXCEPTIONS AND LOGGING
that the URL object’s string representation (toString) is a match for the URL
string argument.
public void testSessionUrl() {
final String url = "http://course.langrsoft.com/cmsc300";
session.setUrl(url);
assertEquals(url, session.getUrl().toString());
}
import java.util.*;
import java.net.*;
The code phrase throws MalformedURLException says that the method setUrl can
possibly generate an exception. Any code that calls the setUrl method must ei-
ther be enclosed in a try-catch block or it must similarly appear in a method
that contains an appropriate throws clause.
The test itself, then, must either catch MalformedURLException or de-
clare that it throws MalformedURLException. Compile to see for yourself.
Exception
Exception Hierarchy Hierarchy
Throwable
Error Exception
RuntimeException
The checked and unchecked exceptions you will work with derive from the
class Exception.
Sun reserves the Error class for serious problems that can go wrong with
the Java environment itself. Errors are unchecked, and you should not have
any usual reason for catching instances of the Error class. Error exception
types include things such as OutOfMemoryError and InternalError. You are
not expected to write code to recover from any of these situations.
The Exception class is the superclass of all other exception types. Sun defines
exceptions generated by the VM or the Java class library as Exception sub-
classes. Any exceptions that you define should also be Exception subclasses.
Subclasses of Exception are sometimes referred to as application exceptions.
Unchecked application exceptions must derive from RuntimeException,
which in turn directly subclasses Exception.
Figure 8.1 shows the core of the exception hierarchy.
The first step toward realizing this test is creating a new Exception sub-
class. Before extending Exception directly, you should peruse the Exception
types that Sun provides in the Java class library. Look for a similar Exception
type on which you might want to base yours. In this case, java.lang.Ille-
galArgumentException is the most appropriate place to start. Create your
StudentNameFormatException class as a subclass of IllegalArgumentExcep-
tion:
package sis.studentinfo;
That’s all you need to do to define the exception type. Your new test
should now compile and fail, since you do not yet generate the exception in
the code. To make the test pass:
public Student(String fullName) {
this.name = fullName;
credits = 0;
List<String> nameParts = split(fullName);
final int maximumNumberOfNameParts = 3;
if (nameParts.size() > maximumNumberOfNameParts)
throw new StudentNameFormatException();
setName(nameParts); Creating Your
} Own Exception
Type
The code demonstrates that you create a new Exception object just like any
other object:
throw new StudentNameFormatException();
278 EXCEPTIONS AND LOGGING
3
[Venners2003].
MESSAGES 279
try {
doSomething();
}
catch (Exception e) {
// log or do nothing
}
Messages
Most exceptions store an associated message. The message is more often than
not for developer consumption. Only sometimes is the message appropriate
to present to an end user of an application (although the message might be
used as the determining basis for an appropriate end user message). You typi-
cally use the message for debugging purposes. Often you will record the mes-
sage in an application log file to help document what caused a problem.5
The class Exception allows you to create a new exception object with or
without a message. You can retrieve the message from an exception using
the method getMessage. For an exception object created without a message,
getMessage returns null.
You may want to store an error message within a StudentNameFormatEx-
ception object that provides more information.
public void testBadlyFormattedName() {
try {
new Student("a b c d");
fail("expected exception from 4-part name");
}
catch (StudentNameFormatException expectedException) {
assertEquals(
"Student name ‘a b c d’ contains more than 3 parts",
expectedException.getMessage()); Messages
}
}
4
[Wiki2004a].
5
See the second half of this lesson for a detailed discussion of logging.
280 EXCEPTIONS AND LOGGING
The Student constructor can then put together a message and pass it off to
the exception constructor.
public Student(String fullName) {
this.name = fullName;
credits = 0;
List<String> nameParts = split(fullName);
final int maximumNumberOfNameParts = 3;
if (nameParts.size() > maximumNumberOfNameParts) {
String message =
"Student name '" + fullName +
"' contains more than " + maximumNumberOfNameParts +
" parts";
throw new StudentNameFormatException(message);
}
setName(nameParts);
}
The exception message string does represent duplication between the test
and code. You may refactor it now if you choose. Later in this lesson, you
will learn how to dynamically construct the message using the String method
format. I will refactor the code at that time.
Catching
Multiple Catching Multiple Exceptions
Exceptions
It is possible for a single line of code to generate more than one exception.
Each exception can be of a different exception type. A method that throws
more than one exception can list each exception type individually:
CATCHING MULTIPLE EXCEPTIONS 281
If both exceptions derive from the same class, you can instead declare that
the method throws an exception of the common supertype:
The try-catch block can have multiple catch clauses, each representing an ex-
ception that might be thrown.
try {
new Student("a b c d");
}
catch (StudentNameFormatException e) {
...
}
catch (NoSuchElementException e) {
...
}
If code within the try block throws an exception, the Java VM transfers
control to the first catch block. If the type of the exception thrown matches
the type declared in the catch clause, the VM executes code within that catch
block. Otherwise, the VM transfers control to the next catch block, and so on.
Once code in a catch block is executed, the VM ignores all other catch blocks.
In the following code, the second catch block catches any exception that is
of type Exception. Since all (normal) exceptions extend from Exception, this
“catch-all” block will trap any application exception other than Student-
NameFormatException, which you already trapped with the first catch block.
try {
new Student("a b c d");
}
catch (StudentNameFormatException e) {
...
}
catch (Exception e) {
...
}
exception. The problem is that you might be unaware of the new exception,
even if it is a checked exception. Hiding errors is bad.
Rethrowing Exceptions
Nothing prevents you from catching an exception and then throwing it (or
another exception) from within the catch block. This is known as rethrowing
an exception.
A common reason to rethrow an exception is to catch it as close to its
source as possible, log it, and then propagate it again. This technique can
make it easier to determine the source of a problem.
public void setUrl(String urlString) throws MalformedURLException {
try {
this.url = new URL(urlString);
}
catch (MalformedURLException e) {
log(e);
throw e;
}
}
In the above changes to setUrl, code in the catch block passes the caught ex-
ception object to the log method, then rethrows it. A refinement of this tech-
nique is to create and throw a new exception of a more application-specific
type. Doing so can encapsulate the specific implementation details that
caused the exception.
Instead of throwing a MalformedURLException, you want to throw an
instance of the application-specific exception type SessionException. Once
you have created the exception class:
package sis.studentinfo;
session.setUrl(url);
assertEquals(url, session.getUrl().toString());
}
Finally, you can change the production code to throw the new exception
instead.
The downside is that you lose information with this solution: If an excep-
tion is thrown, what is the true reason? In a different circumstance, code in
the try block might throw more than one kind of exception. Rethrowing the
application-specific exception would hide the root cause.
As a solution, you could extract the message from the root exception and
store it in the SessionException instance. You would still lose the original
stack trace information, however.
In J2SE 1.4, Sun added the ability to the Throwable class to store a root
cause. The Throwable class provides two additional constructor forms: one
that takes a Throwable as a parameter and another that takes both a message
string and a Throwable as a parameter. Sun also added these constructors to
the Exception and RuntimeException derivatives of Throwable. You may
also set the root cause in the Throwable object after its construction via the
method initCause. Later, you can retrieve the Throwable object from the excep- Rethrowing
tion by sending it the message getCause. Exceptions
First, modify testInvalidSessionUrl to extract the cause from the SessionExcep-
tion instance. As part of the test verification, ensure that the cause of the ex-
ception is the reason expected.
284 EXCEPTIONS AND LOGGING
The assertEquals statement ensures that the class of the cause is Malformed-
URLException. The code to make this comparison demonstrates some reflec-
tive capabilities of Java—the ability of the language to dynamically derive
information about types and their definitions at runtime. I will explain reflec-
tion capabilities in further depth in Lesson 12.
In short, you can send all objects the message getClass, which returns an
appropriate class constant. A class constant is a class name followed by .class.
MalformedURLException.class is a class constant that represents the Malformed-
URLException class.
The test will fail: If no cause is explicitly set, getCause returns null. You will
first need to modify SessionException to capture the cause upon construc-
tion:
package studentinfo;
You can then change the production code to embed the cause in the
SessionException instance.
Stack Traces
Lesson 4 taught you how to read the exception stack to help decipher the
source of an exception.
You can send the stack trace stored within an exception to a print stream
or print writer using the printStackTrace method defined in Throwable. One im-
plementation of printStackTrace takes no parameters, printing the stack trace on
the system console (System.out) by default. A crude implementation of the
log method might do just that.
(Try it.) For a production system, you will want a more robust logging so-
lution. Sun introduced a logging API in J2SE 1.4; I discuss logging in the sec-
ond half of this lesson.
The stack trace representation printed by printStackTrace contains informa-
tion that might be useful for more advanced programming needs. You could
parse the stack trace yourself in order to obtain the information, which many
developers have done. Or, as of J2SE 1.4, you can send the message getStack-
Trace to the exception object in order to access the information in already
parsed form.
(Note that the example is here for demonstration purposes only. Refer to
Additional Lesson III for a brief overview of interacting with databases via
JDBC.)
Both the lookup method and the getConnection method can throw an SQL-
Exception object. You call the the lookup method after obtaining a connection.
If the lookup method subsequently throws an exception, you want to ensure
that the database connection gets closed.
If no exception is thrown, Java transfers control to the finally block upon
completion of the code in the try block. In the example, the finally block
makes a call to close the database connection. If an exception is thrown, Java
executes code in the catch block. As soon as either the catch block completes or
the VM directs control out of the catch block (in the above example, through
use of the throw statement), Java executes the finally block, which closes the
connection.
If you supply a finally block, the catch block itself is optional. For example,
you may not want to attempt to handle an SQL exception from within findBy-
LastName. Instead, you declare that findByLastName can throw an SQLException.
But you still need to close the connection before the VM transfers control out
of findByLastName:
Refactoring
Creating a string to present to the user often involves concatenating several
substrings and printable representations of primitives and objects. The Stu-
dent constructor provides an example:
and safety. Do not assume that a Java format specifier works the same as an
equivalent C format specifier.
Start with testBadlyFormattedName and replace the concatenation with a call to
format. Also, introduce a class constant for the maximum number of name
parts allowed.
The call to format has three arguments: the format string and two format
string arguments. Within the format string are two format specifiers: %s and
%d. The first format specifier corresponds to the first argument, studentName.
The second format specifier corresponds to the second argument,
Student.MAX_NAME_PARTS. A format specifier always begins with a percent sign (%)
and ends with a conversion. A conversion is a character or character that
tells the format method how to interpret the corresponding argument.
The character s, as in %s, indicates a string conversion. When the format
method encounters a string conversion in a format specifier, it replaces the
format specifier with the corresponding argument. In the test, format replaces
%s with the contents of studentName:
Refactoring The String method format is a utility method that delegates all the work to
an instance of the java.util.Formatter class. The Formatter class supplies
more than a dozen different conversions, including date conversions and
more-sophisticated conversions for numeric values.
REFACTORING 289
One very useful conversion is n, which stands for “new line.” If you pro-
vide the format specifier %n, the formatter replaces it with the platform-
specific line separator (usually "\n" or "\r\n"). This saves you from having to
provide code that accesses the system line separator property.
Some of the other things Formatter supports:
• internationalization
• the ability to provide arguments in an order that does not match the
order of format specifiers
• sending output incrementally to a StringBuilder or other sink
// StudentTest.java
public void testBadlyFormattedName() {
final String studentName = "a b c d";
try {
new Student(studentName);
fail("expected exception from 4-part name");
}
catch (StudentNameFormatException expectedException) {
assertEquals(
String.format(Student.TOO_MANY_NAME_PARTS_MSG,
studentName, Student.MAX_NAME_PARTS),
expectedException.getMessage());
}
}
// Student.java
static final String TOO_MANY_NAME_PARTS_MSG =
"Student name '%s' contains more than %d parts";
...
public Student(String fullName) {
this.name = fullName;
credits = 0;
List<String> nameParts = split(fullName);
Refactoring
if (nameParts.size() > MAX_NAME_PARTS) {
String message =
String.format(Student.TOO_MANY_NAME_PARTS_MSG,
fullName, MAX_NAME_PARTS);
290 EXCEPTIONS AND LOGGING
Logging
In most enterprises, it is valuable to track interesting history as applications
execute. The job of logging code is to record significant events and data sup-
porting those events. As a developer, it is your job to determine what is rele-
vant enough to track. Once you make this determination, you insert code at
judicious points in the application to write the information out to (typically)
a log file or files. You later can use the logs to answer questions about perfor-
mance, problems, and so on.
Figuring out just what to track is the hard part. Currently, you are logging
nothing. You lose information on any errors that occur. The lack of relevant
problem data can deprive you of the ability to solve problems. Worse, you
may not know you have a problem until it is too late (this is one reason to
avoid empty catch blocks).
Minimally, you should log all unexpected error conditions (exceptions).
You may also want to log critical calculations, execution of troublesome rou-
tines, or interesting data. You can also choose to log virtually everything that
occurs, such as every time a method executes.
Logging everything, or logging too much, has its problems. Log files can
grow at an extreme rate, particularly as use of the system increases. Logging
has a minor performance penalty; excessive logging can lead to system slow-
down. A worse problem, however, is that if there is too much information
being logged, you will not be able to physically analyze the voluminous
amount of data. Problems may get lost in the forest. Logging becomes use-
less. On the code side, logging has a cluttering effect and can bloat your
code.
It will be up to you to determine how much to log. In some circumstances,
where it is easy for you to deploy updated code (for example, a web applica-
tion), err on the side of logging too little information. Where it is difficult to
Logging update deployed code, err on the side of logging too much information. Hav-
ing more information will increase your prospects of solving a problem. Err
on the side of logging too much information when you know that a section
of code has problems or when the code presents a high risk (for example,
code that controls critical components).
LOGGING IN JAVA 291
Logging in Java
Java supplies complete logging facilities in the package java.util.logging.6 For
an exercise, you will learn to use the logging package to capture exception
events. The Student constructor throws an exception. Let’s log it. Here is a
slightly modified Student constructor with a placeholder for logging.
The first question is how to test that an appropriate message gets logged
when you throw the exception. Or should you bother with such a test?
Remember the first rule of testing: Test everything that can possibly break.
Logging can definitely break. Take a stab at a test:
6
While Java’s logging facilities are adequate for most needs, many developers prefer
to use the freely available Log4J package (http://logging.apache.org/log4j/docs/).
292 EXCEPTIONS AND LOGGING
The Logger class provides a method that corresponds to each level. From high-
est to lowest, the levels are severe, warning, info, config, fine, finer, and finest.
Each logger object maintains its own logging level. If you attempt to log a
message at a lower level than the logger’s level, the logger discards the mes-
sage. For example, if a logger is set to warning, it logs only severe and warn-
ing messages.
You can obtain and set the logging level on a logger using getLevel and
setLevel. You represent the logging level with a Level object. The Level class
defines a set of Level class constants, one to represent each logging level. The
Level class also supplies two additional class constants: Level.ALL and Level.OFF,
to designate either logging all messages or logging no messages.
Modify the wasLogged method in StudentTest to return false. Run all your
tests. By returning false, you cause testBadlyFormattedName to fail. This failure re-
minds you that you will need to flesh out the test.
On the console, you should see something like:
* publishes
Logger Handler LogRecord
Testing Logging
Now your problem is figuring out how to test the logging. First, however, as
I promised, you must throw away the spike code in Student. Discard the log
method and update the constructor by removing the call to log.
Don’t forget that writing code without tests is a good way of getting into
trouble. Make sure you specify what you’re going to code first.
While you have successfully logged a message, its output went to the con-
sole. How are you going to intercept this message for the purpose of writing
a test?
The logging facility lets you direct messages to destinations other than the
console. You can also direct logging to more than one destination at a time.7
The Logger stores handler objects that it uses to determine the logging desti-
nations. A handler object is a subclass of java.util.logging.Handler. The class
ConsoleHandler represents the behavior of sending output to the console.
Most shops prefer to redirect logging output to files, using a FileHandler, so
that developers can later peruse the files. You can tell the logger to route mes-
sages to alternate destinations by sending it Handler objects.
Unfortunately, you wouldn’t be able to write test code that reads a log file,
since you haven’t learned about Java IO yet (you will in Lesson 11). Instead,
you will create a new handler class, TestHandler. Within this handler class,
you will trap all messages being logged. You will provide a getter method
Testing Logging
7
The UML diagram in Figure 8.2 shows that a Logger can have multiple Handler ob-
jects by using an asterisk (*) at the end of the navigable association between the two
classes. The * is a multiplicity indicator. The absence of a multiplicity indicates either
1 or that the multiplicity is uninteresting or irrelevant. The relationship between Log-
ger and Handler is a one-to-many relationship.
TESTING LOGGING 295
that returns the last message that was logged. Once you have built this han-
dler class, you can pass an instance of it to the logger.
For each message you log, the Logger object calls the method publish on the
Handler object, passing it a java.util.logging.LogRecord parameter object.
It is the publish method that you will override in your Handler subclass to
trap the message being logged. You must also supply definitions for the other
two abstract methods in Handler, flush and close. They can be empty.
package sis.studentinfo;
import java.util.logging.*;
String getMessage() {
return record.getMessage();
}
}
In testBadlyFormattedName, you first obtain the same logger that Student will use.
Since both the test and the code in Student pass the Student class name to
getLogger, they will both reference the same Logger object. Subsequently, you
construct an instance of TestHandler and add it as a handler to the logger.
public void testBadlyFormattedName() {
Logger logger = Logger.getLogger(Student.class.getName());
TestHandler handler = new TestHandler();
logger.addHandler(handler);
...
}
Any messages you log will be routed to the TestHandler object. To prove
that Student code indeed logs a message, you can ask the handler for the last
message it received.
public void testBadlyFormattedName() {
...
final String studentName = "a b c d"; Testing Logging
try {
new Student(studentName);
fail("expected exception from 4-part name");
}
catch (StudentNameFormatException expectedException) {
String message =
296 EXCEPTIONS AND LOGGING
String.format(Student.TOO_MANY_NAME_PARTS_MSG,
studentName, Student.MAX_NAME_PARTS);
assertEquals(message, expectedException.getMessage());
assertTrue(wasLogged(message, handler));
}
}
plicate code: Both StudentTest and Student include a complex line of code to
retrieve a Logger object.
In addition to using a Student class variable for the logger, the modified
test inlines the wasLogged method:
package sis.studentinfo;
import java.util.*;
import java.util.logging.*;
Logging to Files
Sun designed the logging facility to allow you to change logging characteris-
tics at runtime. You can quickly route log messages to a file instead of to the
console without having to change, recompile, and redeploy code.
The default behavior is to send logging messages to the console. This be-
havior is not hardcoded somewhere in the Logger class; it is located in an ex-
ternal properties file that you can freely edit.
Browse to the directory in which you installed Java. Within this directory,
you should be able to navigate into the subdirectory jre/lib (or jre\lib if you
are using Windows). Within this subdirectory you should see the file
logging.properties. Edit the file using any editor.
8
You can use properties files in many circumstances to store values that you
may want to change with each execution of an application. Sun chose to
allow you the ability to configure logging behavior using a properties file.
The layout of a properties file should be self explanatory. Comment lines
begin with the pound sign (#). Blank lines are ignored. The remainder of the
lines are key-value pairs, like entries in a hash table. Each key-value pair, or
property, appears in the form:
key = value
Swap the commenting around. Comment out the line that sets handlers to
only the ConsoleHandler. Uncomment the line that sets the property handlers
to both the FileHandler and the ConsoleHandler. By doing so, you tell the
logger to route output to objects of each handler type.
Nearer to the bottom of logging.properties, you will find these lines:
Logging to Files
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
8
If you don’t have this file, you can create it. Use the bits of content that I demonstrate.
LOGGING TO FILES 299
The logging facility uses these lines to determine how the FileHandler
works.
The value for the java.util.logging.FileHandler.pattern is a pattern for naming
the log file. The %h and %u that currently appear in the pattern are known as
fields. The %h field tells the FileHandler to store log files in your home direc-
tory.
The file handler replaces the %u field in the pattern with a unique number.
The goal is to avoid the conflict of two log files sharing the same filename.
Rerun your tests and then navigate to your home directory. Under most
versions of Windows, you can do so using the command:9
cd %USERPROFILE%
Under most Unix shells you can navigate to your home directory using the
command:
cd $HOME
Within your home directory, you should see a file named java0.log. The num-
ber 0 is the unique number replacement for the %u field.
View the contents of java0.log. They should look similar to this:
<?xml version="1.0" encoding="windows-1252" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2004-04-15T03:27:05</date>
<millis>1082021225078</millis>
<sequence>0</sequence>
<logger>sis.studentinfo.Student</logger>
<level>INFO</level>
<class>sis.studentinfo.Student</class>
<method>log</method>
<thread>10</thread>
<message>Student name ‘a b c d’ contains more than 3 parts</message>
</record>
</log>
It’s not the two lines of logging message you expected. Instead, the mes-
sage appears in XML format.10 The logging facility allows you attach a
different formatter to each handler. A formatter is a subclass of java.util.log-
ging.Formatter; Sun supplies two formatter implementations. SimpleFormat-
Logging to Files
9
You must change to the drive on which the user profile is located. You can view the
full path of your home directory, including the drive on which it is located, by execut-
ing set USERPROFILE.
10
eXtensible Markup Language. See http://www.w3.org/XML/.
300 EXCEPTIONS AND LOGGING
ter produces the two log lines you saw earlier. XMLFormatter produces the
output you’re currently seeing in java0.log. You can code your own formatter
class to produce log output however you wish.
Find the java.util.logging.FileHandler.formatter property in logging.properties. Its
current value is java.util.logging.XMLFormatter. Change the value to java.util
.logging.SimpleFormatter and rerun your tests.
The contents of java0.log should now look the same as the output produced
by the ConsoleHandler.
Or you could consider that this test falls into the scenario of what is
Testing known as integration testing. (Some shops may refer to this as customer test-
Philosophy for
ing, system testing, or acceptance testing.) You have already testing that your
Logging
unit of code—Student—interacts with the logging facility correctly. Testing
11
. . . once you learned how to write code that works with files. See Lesson 11.
TESTING PHILOSOPHY FOR LOGGING 301
More on FileHandler
FileHandler defines more fields, including %t and %g. FileHandler code replaces
occurrences of the %t field in the name pattern with the system temporary di-
rectory. On Windows, this is usually c:\temp; on Unix, this is usually /tmp.
The FileHandler code replaces occurrences of the %g field with a generation
number. You use this field in conjunction with the java.util.logging.File-
Handler.count (I’ll refer to this as the shortened property-name count) and
java.util.logging.FileHandler.limit (limit) properties. The limit property represents
how many bytes will be stored in a log file before it reaches its limit. A limit
value of 0 means there is no limit. When a log file reaches its limit, the File-
Handler object closes it. The count field specifies how many log files that File-
Handler cycles through. If the value of count is 1, the FileHandler will reuse the
same log file each time it reaches its limit.
Otherwise the FileHandler tracks a generation number. The first log file
uses a generation number of 0 in place of the %g field. With a log filename pat-
tern of java%g.log, the first generated log filename is java0.log. Each time a log
file reaches capacity, the FileHandler bumps up the generation number and
uses this to replace the %g field in the filename pattern. The FileHandler writes
subsequent log entries to the log file with this new name. The second log file
is java1.log. Once the FileHandler closes count log files, it resets the generation
number to 0.
Logging Levels
You may want to log messages for special purposes, such as for timing how
long a critical method takes to execute or for introducing “trace” messages
that allow you as a developer to follow the flow of execution in the system.
You may not want these messages to normally appear in the logs when the
application is run in production. Instead, you may need the ability to “turn
on” these kinds of messages from time to time without having to recode, re-
compile, and redistribute the application.
As mentioned earlier, the Java logging API supports seven logging levels:
Logging Levels severe, warning, info, config, fine, finer, and finest. You should restrict nor-
mal production logging to severe, warning, and info levels. Reserve the other
levels for special, ephemeral logging needs.
Suppose you need to log the execution time of the Student method getGpa. You
are concerned that it may not perform well under heavy load. Introduce logging
messages at the beginning and end of getGpa, using the fine Logger method.
LOGGING LEVELS 303
double getGpa() {
Student.logger.fine("begin getGpa " + System.currentTimeMillis());
if (grades.isEmpty())
return 0.0;
double total = 0.0;
for (Grade grade: grades)
total += gradingStrategy.getGradePointsFor(grade);
double result = total / grades.size();
Student.logger.fine("end getGpa " + System.currentTimeMillis());
return result;
}
Recompile and rerun your tests.12 You won’t see these new logging mes-
sages either on your console or within a log file.
In order to view the logging messages, you must configure the handler
to accept messages at the lower logging level. You typically do this within
logging.properties. Edit the logging.properties file and make the modifications
noted in bold:
...
.level= FINE
############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################
# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
...
You designate both the global level (.level) and the FileHandler level
(java.util.logging.FileHandler.level) to accept any message logged at a FINE or
higher level. The logging facility first ensures that the request level is as high
as the global level; if not, it does not send the message to the handler. If the
logger sends the message to the handler, the handler similarly determines
whether the message should be logged. Logging Levels
In other words, the global should either be the same level or lower than
any handler levels. If the reverse is true, no messages will be logged by a
12
Here I’ve chosen to take a lazy logging route—no testing—because these are tempo-
rary log messages.
304 EXCEPTIONS AND LOGGING
handler. For example, if you were to set the global level to INFO and the File-
Handler to FINE, the FileHandler would never log any messages.
Rerun the tests one more time and ensure that the timing log messages ap-
pear in the log file but not on the console.
Logging Hierarchies
When you retrieve a logger using the Logger class method getLogger, the Logger
code returns either a new Logger object or an existing Logger object if the
Logger has already created one with the same name. Calling Logger.getLogger
(Student.class.getName()) creates a logger with the name "sis.studentinfo.Student".
Using this fully qualified class name for a Logger is usually sufficient.
The following language tests shows how getLogger returns the same Logger
object when called twice with the same name:
The benefit of this hierarchy is that you can set logging levels at a higher
level. For example, you can set the logging level to Level.ALL at the sis logger. A
child logger uses the logging level of its parent if it has none itself.
EXERCISES 305
• The Logger class provides convenience methods for logging special situ-
ations. You can use entering and existing to simplify logging entry to a
method and exits from it. You can use the throwing method to simplify
logging an exception. Refer to the Java API documentation for Logger
for more information.
Exercises Exercises
1. Write a test that calls a method named blowsUp. Code the method blowsUp
to throw a new RuntimeException with the message "Somebody should
catch this!". Run the test. Verify that it fails with an appropriate stack
trace.
306 EXCEPTIONS AND LOGGING
2. Make the test pass without removing the call to blowsUp. Ensure that the
test fails if blowsUp throws no exception.
3. Ensure that the caught exception contains the proper message.
Change the message, and see the test fail. Change it back in order to
keep the tests passing.
4. Create a test that calls a new method named rethrows. The rethrows method
should call blowsUp and catch the exception. It should then wrap the ex-
ception in a new RuntimeException and throw it again. Use getCause to
ensure that the caught exception contains the original exception.
5. Modify the test to expect a new exception type, SimpleException,
when calling the blowsUp method. SimpleException should extend
RuntimeException.
6. Which of the following test methods will not compile? Of those that
compile, which will pass and which will fail?
public void testExceptionOrder1() {
try {
blowsUp();
rethrows();
fail("no exception");
}
catch (SimpleException yours) {
fail("caught wrong exception");
}
catch (RuntimeException success) {
}
}
}
catch (SimpleException yours) {
fail("caught wrong exception");
}
}
}
catch (SimpleException success) {
}
catch (RuntimeException fail) {
fail("caught wrong exception");
}
}
}
catch (Error fail) {
fail("caught exception in wrong place");
}
catch (Throwable success) {
}
}
Dyer:
class Dyer {
Dyer() {
throw new RuntimeException("oops.");
}
}
7. What compiler error would you expect the following code to gener-
ate? Copy it into a test class of your own and make it pass.
public void testWithProblems() {
try {
doSomething();
fail("no exception");
}
catch (Exception success) {}
}
void doSomething() {
throw new Exception("blah");
}
9. Write a method to log an exception to a given log with the stack trace
in reverse order (so that the point of failure is at the tail of the log in-
stead of the head).
10. Create a custom Handler which discards the messages it receives and
counts the number of times that messages were logged at various lev-
els of severity. Use a map to store the count by severity.
11. Create a custom log message formatter that you can optionally con-
struct with a CountingLogHandler. If no CountingLogHandler is
passed in, the formatter should produce output in the form:
LEVEL: message
For example:
WARNING: watch out
If a CountingLogHandler is passed in, each message should show
the count for the current level. For example:
WARNING: watch out (WARNING total = 1)
Exercises
Lesson 9 Logical
Operators
In this lesson you will learn more about the hash table data structure classes
in Java. You will also learn about the fundamental concept of equality. Un-
derstanding these two fundamental, related topics is critical to your ability to
master Java. Yet many developers with years of Java experience do not fully
understand these concepts. Insidious defects and severe performance issues
can arise from this lack of education. Before we start on these important top-
ics, however, you’ll learn about logical operators, an equally important topic.
You will learn about:
• logical operators
• hash tables
• equality
• toString
Logical Operators
Suppose you want to execute a line of code only if two separate conditions
both hold true. You can use cascading if statements to guard that line of
code:
if (isFullTime)
if (isInState)
rate *= 0.9;
311
312 MAPS AND EQUALITY
Logical Using logical operators, you can combine multiple conditionals into a sin-
Operators gle complex boolean expression:
if (isFullTime && isInState)
rate *= 0.9;
The double ampersands symbol (&&) is the logical and operator. It is a bi-
nary operator—it separates two boolean expressions—the operands. If the
result of both operands is true, the result of the entire conditional is true. In
any other case, the result of the entire conditional is false. Here is the test-
driven truth table (TDTT1) that represents the possible combinations of the
logical and operator:
assertTrue(true && true);
assertFalse(true && false);
assertFalse(false && true);
assertFalse(false && false);
The logical or operator (||), also a binary operator, returns true if either
one of the two operands returns true or if both operands return true. The logi-
cal or operator returns false only if both operands are false. The TDTT for
logical or:
assertTrue(true || true);
assertTrue(true || false);
assertTrue(false || true);
assertFalse(false || false);
The logical not operator (!) is a unary operator that reverse the boolean
value of an expression. If the source expression is true, the not operator re-
sults in the entire expression returning false, and vice versa. The TDTT:
assertFalse(!true);
assertTrue(!false);
The logical exclusive-or (xor) operator (^) is a less frequently used binary
operator that returns false if both operands result in the same boolean value.
The TDTT:
assertFalse(true ^ true);
assertTrue(true ^ false);
assertTrue(false ^ true);
assertFalse(false ^ false);
You can combine multiple logical operators. If you do not provide parenthe-
ses, the order of precedence is !, then &&, then ^^, then ||.
1
A fabricated but cute acronym.
HASH TABLES 313
Hash Tables
Short-Circuiting
The && and || logical operators just shown are known as short-circuited logi-
cal operators. When the Java VM executes an expression using a short-
circuited logical operator, it evaluates the left operand (the expression to the
left of the operator) of the expression first. The result of the left operand may
immediately determine the result of the entire expression.
In the case of or (||), if the left operand is true, there is no need to evaluate
the right operand. The entire expression will always be true. The Java VM
saves time by executing only half of the expression. With respect to and (&&),
if the left operand is false, the entire expression will always be false. The Java
VM will not evaluate the right operand.
You may have a rare need to always execute both sides of the expression.
The code on the right-hand side may do something that you need to always
happen. This is poor design. Lesson 4 points out that methods should either
effect actions or return information, but not both. Likewise, operands should
be viewed as atomic operations that are either commands or queries. You
should be able to restructure the code so that the action occurs before execut-
ing the complex conditional.
If you still feel compelled to always execute both operands, you can use
non-short-circuited logical operators. The non-short-circuited and operator is
a single ampersand (&). The non-short-circuited or operator is a single pipe (|).
Note that xor (^) is always non-short-circuited. Java cannot discern the re-
sult of an xor expression from only one operand. Refer to the TDTT above
to see why.
Hash Tables
You need to create a student directory to contain students enrolled in
the university. The system must allow each student to be assigned a
unique identifier, or ID. The system must allow you to later retrieve
each student from the directory using its ID.
To build this story, you will create a StudentDirectory class. While you could
use an ArrayList to store all students, you will instead use a Map. You learned a
bit about maps in Lesson 6. As a reminder, Map is the interface for a data struc-
ture that allows elements to be stored and received based on a key. A map is a
collection of key-value pairs. You store, or put, an element at a certain key in a
map. You later retrieve, or get, that element by supplying the proper key.
314 MAPS AND EQUALITY
Hash Tables In Lesson 6, you learned about the EnumMap implementation of the Map
interface. If the keys in a Map are of enum type, you should use an
EnumMap. Otherwise you will usually want to use the workhorse implemen-
tation of Map, java.util.HashMap. HashMap is a general-use map based on
a data structure known as a hash table. Most Map code you encounter will
use the HashMap implementation (or the legacy Hashtable implementa-
tion—see Lesson 7).
A hash table provides a specific way to implement a map. It is designed for
rapid insertion and retrieval.
To build the student directory, start with the test:
package sis.studentinfo;
import junit.framework.*;
import java.io.*;
The test, testStoreAndRetrieve, uses a for loop to construct and insert a number Courses
of students into the StudentDirectory. The test verifies, again using a for loop,
that each student can be found in the directory using the Student directory
method findById.
The StudentDirectory code is brief.
package sis.studentinfo;
import java.util.*;
Courses
A Session represents the teaching of a course for a given start date.
Most courses are taught once per term. Each course may thus be rep-
resented by many Session instances.2
Currently the Session class contains the department and number informa-
tion for the associated course, as well as its number of credits. If there can be
multiple sessions of a course, this implementation is inefficient and trouble-
2
This is a small school, so we’re ignoring the possibility that there may need to be
two separate sections of a session in order to accommodate a large number of
students.
316 MAPS AND EQUALITY
Courses
Session * Course
some, since the course information will be repeated throughout each Session
object.
What you want instead is a way of tying many session objects back to a
single Course object. The UML diagram in Figure 9.1 shows this many-to-
one relationship from Session to a new class named Course.
You will refactor your code to the above relationship. Start by creating a
simple Course class that captures the department, number, and number of
credits. A simple test declares that department and number are required to
create a Course. The department and number combined represent the unique
key for a Course. Two separate courses cannot have the same department
and number.
package sis.studentinfo;
import junit.framework.*;
Course starts out as a simple data object. It contains a constructor and two
getters for the key fields.
package sis.studentinfo;
Refactoring Session
Here’s an overview of the steps you’ll undertake in this refactoring:
You will make these changes incrementally. With each change, you’ll use the
compiler to help you find problems, then run your tests to ensure that you
didn’t break anything.
Starting in SessionTest:
Currently, the abstract method createSession constructs Session objects using
department and number strings:
You want to change this creation method to reflect the need to construct Ses-
sion objects using a Course object:
This change will necessitate quite a few other changes, but overall it
shouldn’t take more than 5 minutes to make them. In SessionTest, you will
need to change both the setUp method and testComparable. Here’s the existing
setUp method in SessionTest (I’ve boldfaced the code that will need to change):
318 MAPS AND EQUALITY
I’ve shown the change to setUp here; changes to testComparable are similar:
Use the compiler to guide you through making these changes. As you fix one
problem, the compiler can take you to the next problem to fix.
You will need to change both SummerCourseSessionTest and CourseSes-
sionTest. In the interest of incrementalism, try to make the smallest amount
of changes that will get you to a green bar. You can modify both CourseSes-
sionTest and SummerCourseSessionTest without having to touch CourseSes-
sion or SummerCourseSession.
Here are the changes to the tests. This time I’ve included the old lines of
code from the previous version of the code and struck them out so you could
see the difference line to line. I’ll use this approach for the remainder of the
refactoring.
// CourseSessionTest
...
public class CourseSessionTest extends SessionTest {
public void testCourseDates() {
Date startDate = DateUtil.createDate(2003, 1, 6);
Session session = createSession("ENGL", "200", startDate);
Session session = createSession(createCourse(), startDate);
Date sixteenWeeksOut = createDate(2003, 4, 25);
assertEquals(sixteenWeeksOut, session.getEndDate());
}
// SummerCourseSessionTest.java
package sis.summer;
import junit.framework.*;
import java.util.*;
import sis.studentinfo.*;
Tests should pass at this point. Now modify the Session subclass creation
methods to take a Course directly.
// CourseSessionTest.java
// SummerCourseSessionTest.java
protected Session createSession(Course course, Date date) {
return SummerCourseSession.create(
course.getDepartment(), course.getNumber(), date);
return SummerCourseSession.create(course, date);
}
320 MAPS AND EQUALITY
import junit.framework.TestCase;
import sis.studentinfo.*;
import static sis.report.ReportConstant.NEWLINE;
package sis.report;
import junit.framework.*;
import java.util.*;
import sis.studentinfo.*;
import static sis.report.ReportConstant.NEWLINE;
3
I’ve taken some liberties by updating both RosterReporterTest and CourseRe-
portTest with Java features you have learned since originally coding them. For exam-
ple, the code now assigns a CourseSession object to a Session reference. Doing so will
also force you to change to RosterReporter and CourseReport.
REFACTORING SESSION 321
assertEquals(
Refactoring
String.format(
Session
"CZEC 200%n" +
"CZEC 220%n" +
"ENGL 101%n" +
"ITAL 330%n" +
"ITAL 410%n"),
report.text());
}
// SummerCourseSession.java
public static SummerCourseSession create(
String department, String number, Date startDate) {
return new SummerCourseSession(department, number, startDate);
}
public static Session create(Course course, Date startDate) {
return new SummerCourseSession(
course.getDepartment(), course.getNumber(), startDate);
}
Tests pass. Now push the course into the constructor and rerun the tests.
// CourseSession.java
public static Session create(Course course, Date startDate) {
incrementCount();
return new CourseSession(course, startDate);
}
protected CourseSession(
String department, String number, Date startDate) {
super(department, number, startDate);
}
322 MAPS AND EQUALITY
// SummerCourseSession.java
public static Session create(Course course, Date startDate) {
return new SummerCourseSession(course, startDate);
}
private SummerCourseSession(
String department, String number, Date startDate) {
super(department, number, startDate);
}
private SummerCourseSession(Course course, Date startDate) {
super(course.getDepartment(), course.getNumber(), startDate);
}
Tedious? Perhaps. Simple? Each one of the steps in this refactoring is very
basic and takes only seconds to execute. Safe? Extremely. The overall time
expenditure should only be a few minutes before everything is working. You
get to see a green bar several times during the gradual code transformation.
The foremost authority on refactoring, Martin Fowler, suggests that you can
never run your tests too frequently.4
The alternative would be to make the changes all at once and hope they
work. You might save a couple of minutes, but there is a likely possibility
that you will make a mistake. Correcting the mistake will take you more time
than you saved.
You’re almost there. Push the Course object into the superclass Session
constructor.
// CourseSession.java
protected CourseSession(Course course, Date startDate) {
super(course, startDate);
}
// SummerCourseSession.java
private SummerCourseSession(Course course, Date startDate) {
super(course, startDate);
}
// Session.java
protected Session(
String department, String number, Date startDate) {
this.department = department;
this.number = number;
this.startDate = startDate;
}
4
[Fowler2000], p. 94.
EQUALITY 323
Finally, you can change the Session class to store a Course reference instead
of the separate department and number String references.
// Session.java
abstract public class Session implements Iterable<Student> {
private String department;
private String number;
private Course course;
// ...
protected Session(Course course, Date startDate) {
this.course = course;
this.startDate = startDate;
}
String getDepartment() {
return department;
return course.getDepartment();
}
String getNumber() {
return number;
return course.getNumber();
}
// ...
Equality
In an upcoming lesson, you will build a course catalog. One require-
ment for the course catalog will be to ensure that duplicate courses
are not added to the catalog. Courses are semantically equal if they
have the same department and course number.
Now that you have a functional Course class, you will want to override
the equals method to supply a semantic definition for equality. If you have
two Course objects and the department and number fields in both objects
contain the same (corresponding) String, comparing the two should return
true.
// in CourseTest.java:
public void testEquality() {
324 MAPS AND EQUALITY
This test fails. Remember from Lesson 1 that the object courseA is a separate
object in memory, distinct from courseAPrime. The assertEquals method translates
roughly5 to the underlying lines of code:
if (!courseA.equals(courseAPrime))
fail();
In other words, the comparison between two objects in assertEquals uses the
equals method defined on the receiver. The receiver is the first parameter,
which JUnit refers to as the expected value. The receiver in this example is
courseA.
You have not yet defined an equals method in Course. Java therefore
searches up the hierarchy chain to find one. The only superclass of Course is
the implicit superclass Object. In Object you will find the default definition
of equals:
package java.lang;
public class Object {
// ...
public boolean equals(Object obj) {
return (this == obj);
}
// ...
}
Memory equivalence exists if two references point to the exact same ob-
ject in memory.
You will want to override the equals method in the Course object. The
Course definition of equals will override the Object definition of equals. The
equals method should return true if the receiver object is semantically equal to
the parameter object. For Course, equals should return true if the department
and course number of the receiver is equal to that of the parameter Course.
Let’s code this slowly, one step at a time. For now, code the equals method
in Course to most simply pass the test:
5
The actual code is slightly more involved.
EQUALITY 325
@Override
Equality
public boolean equals(Object object) {
return true;
}
The equalsmethod must match the signature that assertEquals expects. The
assertEqualsmethod expects to call an equals method that takes an Object as pa-
rameter. If you were to specify that equals took a Course object as parameter,
the assertEquals method would not find it:
@Override
public boolean equals(Course course) { // THIS WILL NOT WORK
return true;
}
Once you see that this test fails, update your equals method:
@Override
public boolean equals(Object object) {
Course that = (Course)object;
return
this.department.equals(that.department) &&
this.number.equals(that.number);
}
You must first cast the object argument to a Course reference so that you
can refer to its instance variables. The local variable name you give to the ar-
gument helps differentiate between the receiver (this) and the argument (that).
The return statement compares this Course’s department and number to the
department and number of that Course. If both (&&) are equal, the equals
method returns true.
326 MAPS AND EQUALITY
Reflexivity: x.equals(x)
The above contract should hold true for all non-null values of x and y. You
can implement each of these rules in your unit test for equality.6
// reflexivity
assertEquals(courseA, courseA);
// transitivity
Course courseAPrime2 = new Course("NURS", "201");
assertEquals(courseAPrime, courseAPrime2);
assertEquals(courseA, courseAPrime2);
// symmetry
assertEquals(courseAPrime, courseA);
// consistency
assertEquals(courseA, courseAPrime);
// comparison to null
assertFalse(courseA.equals(null));
}
6
A nice set of JUnit extensions, located at http://sourceforge.net/projects/junit-addons,
provides an automated means of testing the equality contract.
APPLES AND ORANGES 327
When you run your tests, everything will pass but the final assertion in Apples and
testEquality.
You will receive a NullPointerException: The that argument is null Oranges
and the equals code attempts to access its fields (department and object). You can
add a guard clause to return false if the argument is null:
@Override
public boolean equals(Object object) {
if (object == null)
return false;
Course that = (Course)object;
return
this.department.equals(that.department) &&
this.number.equals(that.number);
}
Even though the String value matches the department and number of
courseA,a Course is not a String. You would expect this comparison to return
false. When Java executes the equals method, however, you receive a ClassCast-
Exception. The line of the equals method that executes the cast is the cause:
@Override
public boolean equals(Object object) {
if (object == null)
return false;
Course that = (Course)object;
7
[Astels2004].
328 MAPS AND EQUALITY
return
Apples and
this.department.equals(that.department) &&
Oranges
this.number.equals(that.number);
}
The code attempts to cast the String parameter to a Course reference. This
invalid cast generates the ClassCastException.
You need a guard clause that immediately returns false if someone throws
an orange at your equals method. The guard clause ensures that the type of the
parameter matches the type of the receiver.
@Override
public boolean equals(Object object) {
if (object == null)
return false;
if (this.getClass() != object.getClass())
return false;
Course that = (Course)object;
return
this.department.equals(that.department) &&
this.number.equals(that.number);
}
You learned about the getClass method in Lesson 8. It returns a class con-
stant. For the example comparison that threw the ClassCastException, the
class constant of the receiver is Course.class, and the class constant of the para-
meter is String.class. Class constants are unique—there is only one “instance”
of the Class object Course.class. This uniqueness allows you to compare class
constants using the operator != (not equal to) instead of comparing them
using equals.
Some developers choose to use the instanceof operator. The instanceof opera-
tor returns true if an object is an instance of, or subclass of, the target class.
Here is the equals method rewritten to use instanceof.
@Override
public boolean equals(Object object) {
if (object == null)
return false;
if (!(object instanceof Course))
return false;
Course that = (Course)object;
return
this.department.equals(that.department) &&
this.number.equals(that.number);
}
You should initially prefer to compare the classes of the receiver and argu-
ment, instead of using instanceof. The class comparison only returns true if both
objects are of the exact same type. Introduce instanceof later if you need to
COLLECTIONS AND EQUALITY 329
compare objects irrespective of their position in an inheritance hierarchy. See Collections and
Lesson 12 for more information on instanceof. Equality
You should be clear on the fact that assertEquals uses the equals method for
reference comparisons. But it can be helpful to explicitly emphasize that fact,
and show that the equals method is what you’re actually testing.
You may want to compare two references to see if they are the same. In
other words, do they point to the same object in memory? You could code
this comparison as:
assertTrue(courseA == courseB);
or as:
assertSame(courseA, courseB);
You should prefer the second assertion form, since it supplies a better
error message upon failure. As before, though, you might choose to explic-
itly show the == comparison if for purposes of building an equals method
test.
import junit.framework.*;
import java.util.*;
...
Hash Tables
// containment
List<Course> list = new ArrayList<Course>();
list.add(courseA);
assertTrue(list.contains(courseAPrime));
}
The contains method defined in ArrayList loops through all contained ob-
jects, comparing each to the parameter using the equals method. This test
should pass with no further changes. In fact, the test for containment is more
of a language test. It tests the functionality of ArrayList, by demonstrating
that ArrayList uses the equals method for testing containment. The contain-
ment test does not add to the equality contract. Feel free to remove it.
What about using the Course as a key in a HashMap object?
public void testEquality() {
Course courseA = new Course("NURS", "201");
Course courseAPrime = new Course("NURS", "201");
// ...
The above bit of test demonstrates a Map method, containsKey. This method
returns true if a matching key exists in the map. Even though a Map is a col-
lection, just as a List is, this test fails! To understand why, you must under-
stand how the HashMap class is implemented.
Hash Tables
An ArrayList encapsulates an array, a contiguous block of space in memory.
Finding an object in an array requires serially traversing the array in order to
find an element. The class java.util.HashMap is built around the hash table
construct mentioned at the beginning of this lesson. It too is based on a con-
tiguous block of space in memory.
I can pictorially represent a hash table as a bunch of slots (Figure 9.2).
When inserting an element into a hash table, the code rapidly determines a
slot for the element by first asking it for its hash code. A hash code is simply
an integer, ideally as unique as possible. The contract for hash code is based
on the definition of equality for a class: If two objects are equal, their hash
HASH TABLES 331
Hash Tables
0
code should be equal. If two objects are not equal, then their hash codes are
ideally (but not necessarily) unequal.
Once the hash code is available, a bit of arithmetic determines the slot:
65 % 10 = 5
Just as in an array, Java calculates the memory address for the start of a
slot using the formula:
Collisions
0
5 :Course
Collisions
The hash code for an object should be as unique as possible. If two unequal
objects return the same hash code, they will resolve to the same slot in the
hash table. This is known as a collision. The implication of a collision is
extra logic and extra time to maintain a list of collided objects. A few solu-
tions for resolving collisions exist. The simplest is manage a list of collided
objects for each slot. Figure 9.4 shows a pictorial representation.
Since the possibility of collisions exists, when retrieving an object, the
hash table code must also compare the object retrieved from a slot to ensure
that it is the one expected. If not, the code must traverse the list of collided
objects to find the match.
If all objects were to return the same hashCode, such as 1, then all objects will
collide to the same slot in a HashMap. The end result would be that every in-
sertion and deletion would require serially traversing the list of collided
AN IDEAL HASH ALGORITHM 333
An Ideal Hash
0
Algorithm
1
6
a :Course b :Course
7
objects. This list would include every object inserted. In this degenerate case,
you might as well use an ArrayList.
If the hash codes of all objects generate different slots, you have an opti-
mally mapped hash table. Performance is the best possible: All insertions to
and retrievals from the table execute in constant time. There is no need to se-
rially traverse a collision list.
An Ideal Hash code algorithms typically use prime numbers. The use of primes tends to give
Algorithm better distribution when combined with the modulus operation against the
table size.
@Override
public int hashCode() {
final int hashMultiplier = 41;
int result = 7;
result = result * hashMultiplier + department.hashCode();
result = result * hashMultiplier + number.hashCode();
return result;
}
The tests should pass once you implement this hashCode method.8 Separate
the hash code tests into a new test method that will enforce the complete con-
tract for hashCode.
public void testHashCode() {
Course courseA = new Course("NURS", "201");
Course courseAPrime = new Course("NURS", "201");
assertEquals(courseA.hashCode(), courseAPrime.hashCode());
// consistency
assertEquals(courseA.hashCode(), courseA.hashCode());
}
The test proves that two semantically equal courses produce the same
hash code. It also demonstrates consistency. The return value from hashCode is
the same (consistent) with each call to it, as long as the key values for the
courses do not change.
The part of the test that demonstrates stuffing the Course into a hash map
can be removed, since the contract assertions for hashCode are all that is re-
quired. The final set of equality and hash code contract tests:
public void testEquality() {
Course courseA = new Course("NURS", "201");
Course courseAPrime = new Course("NURS", "201");
assertEquals(courseA, courseAPrime);
// reflexivity
assertEquals(courseA, courseA);
8
This algorithm was obtained from the example at http://mindprod.com/jgloss/
hashcode.html.
A FINAL NOTE ON HASHCODE 335
// transitivity
A Final Note on
Course courseAPrime2 = new Course("NURS", "201");
hashCode
assertEquals(courseAPrime, courseAPrime2);
assertEquals(courseA, courseAPrime2);
// symmetry
assertEquals(courseAPrime, courseA);
// consistency
assertEquals(courseA, courseAPrime);
// comparison to null
assertFalse(courseA.equals(null));
assertEquals(courseA.hashCode(), courseAPrime.hashCode());
// consistency
assertEquals(courseA.hashCode(), courseA.hashCode());
}
There are many ways to test performance, but the above test provides a
simple, adequate mechanism. The class java.lang.System contains a static
method, currentTimeMillis, that returns the milliseconds representing the current
time stamp. Before starting, you store the current timestamp in a local vari-
able (start). You execute the desired code n times using a for loop. When the
loop completes, you capture the timestamp (stop). You obtain the elapsed
time by subtracting the start time from the stop time. The assertion ensures
that the loop completed in a reasonable amount of time (arbitraryThreshold).
The assertTrue method call contains a String as its first parameter. The
String gets printed by JUnit as part of the error message only if the assertion
fails. In this example, the failure will show how long the loop actually took
to execute.
You may want to isolate performance tests in a separate suite. You will
still want to run them regularly (perhaps daily, or after you have completed a
significant task). The nature of performance tests makes them slow, however,
so you may not want the constant negative impact to the performance of
your functional unit test suite.
If you find you need more comprehensive unit testing, either build your
own framework by eliminating duplication or consider a tool such as Junit-
Perf.9 JUnit itself contains some rudimentary hooks to help you with repeti-
tive testing.
To demonstrate the usefulness of the example performance test, change
the hashCode implementation in Course to return a constant number:
@Override
public int hashCode() {
return 1;
}
9
See http://www.clarkware.com/software/JUnitPerf.html.
MORE ON USING HASHMAPS 337
Even with only 10,000 entries into the HashMap, the test should fail mis- More on Using
erably. Revert the hashCode to its previous form; the test should now pass. HashMaps
Another technique for testing hashCode is to ensure that the variance that a
number of hash codes produces is reasonably high. The variance is a measure
of how spread out a distribution of numbers is. You calculate the variance as
the average of the squared deviation of each number from the mean.
The problem with both of these testing techniques is that they are based
on arbitrary measures of what is acceptable. As a developer, you can deter-
mine the value of this kind of testing and choose to defer it until you can pin-
point a true performance problem to the hash table.
Minimally, you should at least write enough tests to prove that the con-
tract for hashCode holds true.
import java.util.*;
import sis.studentinfo.*;
The following three new tests for ReportCard demonstrate how to iterate
through each of the three possibilities. (These are unnecessary tests, and act
more like language tests.) Each of the three tests show different but valid test-
ing approaches.
package sis.report;
import junit.framework.*;
import sis.studentinfo.*;
import java.util.*;
The first test, testKeys, provides one technique for ensuring that you iterate
through all expected values in a collection. You first create a set and populat-
ing it with the objects (grades in this example) you expect to receive. You
MORE ON USING HASHMAPS 339
then create a secondary set and add to it each element that you iterate More on Using
through. After iteration, you compare the two sets to ensure that they are HashMaps
equal.
Perhaps you remember sets from grade school. (Remember Venn dia-
grams?) If not, no big deal. A set is an unordered collection that contains
unique elements. If you attempt to add a duplicate element to a set, the set re-
jects it. In Java, the java.util.Set interface declares the behavior of a set. The
class java.util.HashSet is the workhorse implementation of the Set interface.
The HashSet class compares objects using equals to determine if a candidate
object is a duplicate.
Based on your understanding of how a hash table works, it should be
clear that you cannot guarantee that its keys will appear in any specific order.
It should also be clear that each key is unique. You cannot have two entries
in a hash table with the same key. As such, when you send the message keySet
to a HashMap object, it returns the keys to you as a set.
As shown in testKeys, you iterate a Set much as you iterate a List, using a
for-each loop.
In contrast, the values in a hash table can be duplicates. For example, you
might choose to print the same message (“get help”) for a student with a D
or an F. A HashMap thus cannot return the values as a Set. Instead, it returns
the values as a java.util.Collection.
Collection is an interface implemented by both HashSet and ArrayList.
The Collection interface declares basic collection functionality, including the
abilities to add objects to a collection and obtain an iterator from a collec-
tion. The Collection interface does not imply any order to a collection, so it
does not declare methods related to indexed access as does the List interface.
Both java.util.Set and java.util.List interfaces extend the Collection interface.
The test, testValues, uses a slightly different technique. You again create a
Set to represent expected values, then add to it each possible value. You
340 MAPS AND EQUALITY
More on Using obtain the hash table values by sending the message values to the HashMap
HashMaps object.
As you iterate through the values, you verify that the list of expected val-
ues contains each element. The contains method uses the equals method to de-
termine if the collection includes an object. Finally, you must also test the size
of the secondary set to ensure that it does not contain more elements than
expected.
The third test, testEntries, shows how you can iterate through the keys and
associated values (the maps) simultaneously. If you send the message entrySet
to a HashMap, it returns a Set of key-value pairs. You can iterate through
this set using a for-each loop. For each pass through the loop you get a
Map.Entry reference that stores a key-value pair. You can retrieve the key
and value from a Map.Entry by using the methods getKey and getValue. The
Map.Entry is bound to the same key type (Student.Grade in the example)
and value type (String) as the HashMap object.
public void testEntries() {
Set<Entry> entries = new HashSet<Entry>();
assertEquals(expectedEntries, entries);
}
You need to test that the for-each loop iterates over all the entries. You can
create a secondary set of expected entries, like in the previous tests, and com-
pare this expected set to the set populated by iteration. To the expected set
you need to add the appropriate key-value entries, but unfortunately,
Map.Entry is an interface. No concrete class to store a key-value pair is avail-
able to you.
Instead, you will create your own Entry class to store a key-value pair. The
process of iteration will instantiate Entry objects and add them to the “actu-
ADDITIONAL HASH TABLE AND SET IMPLEMENTATION 341
als” set. Also, you will populate the expected set with Entry objects. You can Additional Hash
then compare the two sets to ensure that they contain equal Entry objects. Table and Set
Implementations
class Entry {
private Student.Grade grade;
private String message;
Entry(Student.Grade grade, String message) {
this.grade = grade;
this.message = message;
}
@Override
public boolean equals(Object object) {
if (object.getClass() != this.getClass())
return false;
Entry that = (Entry)object;
return
this.grade == that.grade &&
this.message.equals(that.message);
}
@Override
public int hashCode() {
final int hashMultiplier = 41;
int result = 7;
result = result * hashMultiplier + grade.hashCode();
result = result * hashMultiplier + message.hashCode();
return result;
}
}
The class Entry is a quick-and-dirty class used solely for testing. I there-
fore chose to write no tests for it. The Entry class contains an equals method
and its companion method hashCode. Since this is a quick-and-dirty test class, I
might have chosen to omit the hashCode method, but a successful test requires
it. Try running the tests without hashCode, and make sure you understand why
the test fails without it.
Method Description
copyOf (+1 variant) creates an EnumSet using elements from an existing collection
range
TOSTRING 343
almost immediate insertion and retrieval time, as you expect when using a toString
HashMap or HashSet, operation times increase logarithmically as the size of
the collection increases.
IdentityHashMap
An IdentityHashMap uses reference equality (==) to compare keys instead of
equality (equals). You would use this Map implementation if you needed keys
to represent unique instances. This class is not a general-purpose map imple-
mentation since it violates the contract that Maps should compare keys using
equals.
toString
You have seen how you can send the toString message to a StringBuilder object
in order to extract its contents as a String.
The toString method is one of the few methods that the Java designers
chose to define in java.lang.Object. Its primary purpose is to return a print-
able (String) representation of an object. You could use this printable repre-
sentation as part of an end-user application, but don’t. The toString method is
more useful for developers, to help in debugging or deciphering a JUnit
message.
You want objects of the Course class to display a useful string when you
print them out in a JUnit error message. One way would be to extract its ele-
ments individually and produce a concatenated string. Another way is to use
the toString method.
public void testToString() {
Course course = new Course("ENGL", "301");
assertEquals("ENGL 301", course.toString());
}
344 MAPS AND EQUALITY
toString
Since toString is inherited from Object, you get a default implementation
that fails the test:
expected: <ENGL 301> but was: <studentinfo.Course@53742e9>
Ultimately, it’s your call. My usual recommendation is to start out with Strings and
more tests than necessary. Cut back only when they become a problem that Equality
you can’t fix through other means.
java -agentlib:hprof=help
10
This feature is not guaranteed to be part of future versions of Java.
11
[Gamma1995].
346 MAPS AND EQUALITY
Exercises The strings a and b are semantically equal (.equals): Their character content
is the same. The string references also have memory equivalence (==): They
point to the same String object in memory.
Because of this optimization, as a beginning Java programmer it is easy to
think you should compare String objects using ==.
if (name == "David Thomas")
But this is almost always a mistake. It is possible for two strings to contain
the same characters but not be stored in the same memory location. This oc-
curs because Java cannot optimize Strings that are constructed through the
use of StringBuilder or concatenation.12
Exercises
1. Create a String literal using the first two sentences of this exercise.
You will create a WordCount class to parse through the text and
count the number of instances of each word. Punctuation should not
appear in the word list, either as part of a word or as a separate
“word.” Use a map to store the frequencies. The WordCount class
should be able to return a set of strings, each showing the word and
its frequency. Track the frequency without respect to case. In other
12
When compiled, code using the + for String concatenation translates to equivalent
code that uses the StringBuffer class.
EXERCISES 347
words, two words spelled the same but with different case are the Exercises
same word. Hint: You can use the regular expression \W+ in conjunc-
tion with the String split method in order to extract the words from
the String.
2. Create a class, Name, that declares a single string field. Create an
equals method on the class that uses the string for comparisons. Do not
create a hashCode method. Build a test that verifies the contract of equal-
ity for Name.
3. Create a Set<Name> object that contains a variety of Name objects,
including new Name("Foo"). Verify that the Set contains method returns false
if you ask the Set whether or not it contains the Name(“Foo”) instance.
Show that if you create:
Name foo = new Name("Foo");
Mathematics
Mathematics
In this lesson you will learn about the mathematical capabilities of Java. Java
is designed as a multipurpose language. In addition to standard, general-
purpose arithmetic support, Java supports a small number of more-advanced
math concepts such as infinity. Support is provided through a mixture of Java
language features and library classes.
Java supplies a wide range of floating point and integral numerics in both
fixed and arbitrary precision form. The representation of numerics and re-
lated functions in Java generally adheres to IEEE standards. In some cases,
you can choose to use either algorithms designed for speed or algorithms
whose results strictly adhere to published standards.
Topics:
• BigDecimal
• additional integral types and operations
• numeric casting
• expression evaluation order
• NaN (Not a Number)
• infinity
• numeric overflow
• bit manipulation
• java.lang.Math
• static imports
349
350 MATHEMATICS
BigDecimal
The class java.math.BigDecimal allows you to do arbitrary-precision decimal
BigDecimal (base 10) floating-point arithmetic. This means that arithmetic with
BigDecimal works like you learned in grade school. The BigDecimal class pro-
vides a comprehensive set of arithmetic methods and extensive control over
rounding. A BigDecimal object represents a decimal number of arbitrary
precision, meaning that you decide the number of significant digits it will con-
tain. It is immutable—you cannot change the number that a BigDecimal stores.
The most frequent use of BigDecimal is in financial applications. Financial
applications often require you to accurately account for money down to the
last hundredth of a cent or even at more granular levels.
Floating-point numbers, as implemented by the Java primitive types float and
double, do not allow every number to be represented exactly as specified. This is
because it is not mathematically possible to represent certain decimal numbers
(such as 0.1) using binary.1 BigDecimal can exactly represent every number.
There are two major downsides to using BigDecimal. First, hardware nor-
mally optimizes binary floating-point operations, but BigDecimal’s decimal
floating point is implemented in software. You may experience poor perfor-
mance when doing extremely heavy math calculations with BigDecimal.
Second, there is no mathematical operator support for BigDecimal. You
must execute common operations such as addition and multiplication via
method calls. The requisite code is wordy and thus tedious to write and read.
Using BigDecimal
The student information system must maintain an account of
charges and credits for each student. The first test demonstrates the
ability of an Account to track a balance based on applied charges
and credits.
package sis.studentinfo;
import java.math.BigDecimal;
import junit.framework.*;
1
See http://www.alphaworks.ibm.com/aw.nsf/FAQs/bigdecimal.
BIG DECIMAL 351
account.credit(new BigDecimal("0.10"));
account.credit(new BigDecimal("11.00"));
assertEquals(new BigDecimal("11.10"), account.getBalance());
}
}
Big Decimal
The preferred way to construct a new BigDecimal is to pass a String to its
constructor. The String contains the value you want the BigDecimal to repre-
sent. You could also pass a double, but it may not precisely represent the num-
ber you expect, due to the nature of floating-point representation in Java.2 As
a result, the BigDecimal may not precisely represent the number you expect.
You may also construct BigDecimal objects from other BigDecimal ob-
jects. This allows you to directly use an expression returning a BigDecimal as
the constructor parameter.
The Account implementation:
package sis.studentinfo;
import java.math.BigDecimal;
2
See the Java Glossary entry at http://mindprod.com/jgloss/floatingpoint.html for a
discussion of floating-point representation.
352 MATHEMATICS
Scale
In testTransactions, the assertion expects the result to be a BigDecimal with
value "11.10"—with two places after the decimal point. In contrast, you would
Big Decimal expect the result of an equivalent Java float or double operation to be "11.1".
Adding such an assertion:
assertEquals(new BigDecimal("11.1"), account.getBalance());
That is, the scale as the result of adding a 1-scale number to a 3-scale number
is 3.
import java.math.BigDecimal;
More on
public class Account { Primitive
private BigDecimal balance = new BigDecimal("0.00"); Numerics
private int transactionCount = 0;
Review the Java API documentation for details on the other seven rounding
modes.
assertEquals(12, 0xC);
assertEquals(10, 012);
Integral literals are by default of int type. You can force an integral literal to
be a long by suffixing it with the letter L. The lowercase letter l can also be
used, but prefer use of an uppercase L, since the lowercase letter is difficult to
discern from the number 1.
Integer Math
Let’s revisit the code in the class Performance (from Lesson 7) that calculates
the average for a series of tests.
Suppose the total local variable had been declared as an int instead of a
double,which is reasonable since each test score is an int. Adding an int to an
int returns another int.
NUMERIC CASTING 355
If you need the remainder while doing integer division, use the modulus
operator (%).
assertEquals(0, 40 % 8);
assertEquals(3, 13 % 5);
Numeric Casting
Java supports numeric types of different ranges in value. The float type uses
32 bits for its implementation and expresses a smaller range of numbers than
does double, which uses 64 bits. The integral primitive types—char, byte, short,
int, and long—each support a range that is different from the rest of the inte-
gral primitive types.
You can always assign a variable of a smaller primitive type to a larger
primitive type. For example, you can always assign a float reference to a double
reference:
float x = 3.1415f;
double y = x;
You may want to go the other way and cast from integral primitives to
floating-point numbers in order to force noninteger division. A solution to
the above problem of calculating the average for a Performance is to cast the
dividend to a double before applying the divisor:
public double average() {
int total = 0;
for (int score: tests)
total += score;
return (double)total / tests.length;
}
Here are a few examples of how precedence affects the result of an expres-
sion:
assertEquals(7, 3 * 4 - 5); // left to right
assertEquals(-11, 4 - 5 * 3); // multiplication before subtraction
assertEquals(-3, 3 * (4 - 5)); // parentheses evaluate first
NaN
If no test scores exist, the average for a Performance should be zero.
You haven’t yet written a test for this scenario. Add the following
brief test to PerformanceTest.
public void testAverageForNoScores() {
Performance performance = new Performance();
assertEquals(0.0, performance.average());
}
If you run this, oops! You get a NullPointerException. That can be solved
by initializing the integer array of tests defined in Performance to an empty
array:
private int[] tests = {};
You might want the average method to return NaN. But how would you
write the test, since you cannot compare NaN to any other floating-point num-
ber? Java supplies the isNaN static method, defined on both Float and Double,
for this purpose:
public void testAverageForNoScores() {
Performance performance = new Performance();
assertTrue(Double.isNaN(performance.average()));
}
Infinity
The java.lang.Float class provides two constants for infinity: Float.NEGATIVE_
INFINITY and Float.POSITIVE_INFINITY. The class java.lang.Double provides corre-
sponding constants.
While integral division by zero results in an error condition, double and float
operations involving zero division result in the mathematically correct infin-
ity value. The following assertions demonstrate use of the infinity constants.
final float tolerance = 0.5f;
final float x = 1f;
assertEquals(
Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY * 100, tolerance);
NUMERIC OVERFLOW 359
assertEquals(Float.NEGATIVE_INFINITY,
Float.POSITIVE_INFINITY * -1, tolerance);
assertTrue(Float.isNaN(0f / 0f));
assertTrue(Float.isNaN(0f % 0f));
assertEquals(
Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY / x, tolerance);
assertEquals(
Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY / x, tolerance);
assertTrue(Float.isNaN(Float.POSITIVE_INFINITY % x));
assertTrue(
Float.isNaN(Float.POSITIVE_INFINITY / Float.POSITIVE_INFINITY));
assertTrue(
Float.isNaN(Float.POSITIVE_INFINITY % Float.POSITIVE_INFINITY));
assertTrue(
Float.isNaN(Float.POSITIVE_INFINITY / Float.NEGATIVE_INFINITY));
assertTrue(
Float.isNaN(Float.POSITIVE_INFINITY % Float.NEGATIVE_INFINITY));
assertTrue(
Float.isNaN(Float.NEGATIVE_INFINITY / Float.POSITIVE_INFINITY));
assertTrue(
Float.isNaN(Float.NEGATIVE_INFINITY % Float.POSITIVE_INFINITY));
assertTrue(
Float.isNaN(Float.NEGATIVE_INFINITY / Float.NEGATIVE_INFINITY));
assertTrue(
Float.isNaN(Float.NEGATIVE_INFINITY % Float.NEGATIVE_INFINITY));
Numeric Overflow
When working with integral variables, you must be careful to avoid over-
flow, which can create incorrect results.
For each numeric type, Java provides constants representing its range. For
example, the constants Integer.MAX_VALUE and Integer.MIN_VALUE represent the largest
(231 – 1) and smallest (–231) values an int can have, respectively.
Java will internally assign the results of an expression to an appropriately
large primitive type. The example test shows how you can add one to a byte at
its maximum value (127). Java stores the result of the expression as a larger
primitive value.
360 MATHEMATICS
byte b = Byte.MAX_VALUE;
assertEquals(Byte.MAX_VALUE + 1, b + 1);
But if you attempt to assign the result of an expression that is too large
back to the byte, the results are probably not what you expected. The follow-
Bit Manipulation
ing test passes, showing that adding 1 to a byte at maximum value results in
the smallest possible byte:
byte b = Byte.MAX_VALUE;
assertEquals(Byte.MAX_VALUE + 1, b + 1);
b += 1;
assertEquals(Byte.MIN_VALUE, b);
The result is due to the way Java stores the numbers in binary. Any addi-
tional significant bits are lost in an overflow condition.
Floating-point numbers overflow to infinity.
assertTrue(Double.isInfinite(Double.MAX_VALUE * Double.MAX_VALUE));
Bit Manipulation
Java supports bit-level operations on integers. Among myriad other uses, you
might use bit manipulation for mathematical calculations, for performance
(some mathematical operations may be faster using bit shifting), in encryp-
tion, or for working with a compact collection of flags.
Binary Numbers
Your computer ultimately stores all numbers internally as a series of bits. A bit
is represented by a binary, or base 2, number. A binary number is either 0 or 1.
The following table on top of page 381 counts in binary from 0 through 15.
The table also shows the hexadecimal (“hex”) representation of each
number. Java does not allow you to use binary literals, so the easiest way to
understand bit operations is to represent numbers with hexadecimal (base
16) literals. Each hex digit in a hex literal represents four binary digits.
nary—each binary digit adds into a positive sum. Negative integers are stored
in two’s complement form. To obtain the two’s complement representation of
a number, take its positive binary representation, invert all binary digits, and
add 1.
For example, the number 17 is represented in two’s complement as:
3
0000_0000_0000_0000_0000_0000_0001_0001
3
I have added underscores for readability.
362 MATHEMATICS
The bit-or operator (|) is also known as bitwise addition. If both bits are 0,
bitwise addition returns a 0; otherwise it returns a 1. The following TDTT
demonstrates bit-or.
assertEquals(0, 0 | 0);
assertEquals(1, 0 | 1);
assertEquals(1, 1 | 0);
assertEquals(1, 1 | 1);
The logical negation operator (~) flips all bits in the integer so that 0s be-
come 1s and 1s become 0s.
int x = 0x7FFFFFF1; //0111_1111_1111_1111_1111_1111_1111_0001
assertEquals(0x8000000E, ~x); //1000_0000_0000_0000_0000_0000_0000_1110
Java supports the use of compound assignment with logical bit operators.
Thus, x &= 1 is equivalent to x = x & 1.
that the first flag is on and all other flags are off. The binary value 00000101
would mean that the first and third flags are on and all other flags are off.
To set the values of each flag, you first define a mask for each binary posi-
tion. The mask for the first position is 00000001 (decimal 1), the mask for the
second position is 00000010 (decimal 2), and so on. Setting a value is done using Bit Manipulation
bit-or, and extracting a value is done using bit-and.
You need to be able to set four yes-no flags on each student: Does
the student reside on campus, is the student tax-exempt, is the stu-
dent a minor, and is the student a troublemaker? You have 200,000
students and memory is at a premium.
public void testFlags() {
Student student = new Student("a");
student.set(
Student.Flag.ON_CAMPUS,
Student.Flag.TAX_EXEMPT,
Student.Flag.MINOR);
assertTrue(student.isOn(Student.Flag.ON_CAMPUS));
assertTrue(student.isOn(Student.Flag.TAX_EXEMPT));
assertTrue(student.isOn(Student.Flag.MINOR));
assertFalse(student.isOff(Student.Flag.ON_CAMPUS));
assertTrue(student.isOff(Student.Flag.TROUBLEMAKER));
student.unset(Student.Flag.ON_CAMPUS);
assertTrue(student.isOff(Student.Flag.ON_CAMPUS));
assertTrue(student.isOn(Student.Flag.TAX_EXEMPT));
assertTrue(student.isOn(Student.Flag.MINOR));
}
You should normally prefer explicit query methods for each flag (isOnCampus,
isTroublemaker,
etc.). The above approach may be preferable if you have a large
number of flags and more dynamic needs.
The relevant code in Student follows.
public enum Flag {
ON_CAMPUS(1),
TAX_EXEMPT(2),
MINOR(4),
TROUBLEMAKER(8);
Flag(int mask) {
this.mask = mask;
}
}
Each flag is an enum constant, defined by the Flag enum located in Student.
Each enum instantiation passes an integer representing a mask to the construc-
tor of Flag. You store the flags in the int instance variable settings. You explic-
itly initialize this variable to all 0s to demonstrate intent.
The set method takes a variable number of Flag enum objects. It loops
through the array and applies the Flag’s mask to the settings variable by using
a bit-or operator.
The unset method loops through Flag enum objects and bit-ands the negation
of the Flag’s mask to the settings variable.
A demonstration of how the bit-or operation sets the correct bit:
Flag.MINOR 0100
settings 0000
Flag.ON_CAMPUS 0001
The isOn method bit-ands a Flag’s mask with the settings variable.
settings 0000
Flag.MINOR 0100
settings 0101
~Flag.MINOR 1011
Bit Manipulation
settings & ~Flag.MINOR 0001
Using Xor
The xor operator has the unique capability of being reversible.
int x = 5; // 101
int y = 7; // 111
int xPrime = x ^ y; // 010
assertEquals(2, xPrime);
assertEquals(x, xPrime ^ y);
You can also use xor as the basis for parity checking. Transmitting data
introduces the possibility of individual bits getting corrupted. A parity check
involves transmitting additional information that acts like a checksum. You
calculate this checksum by applying an xor against all data sent. The receiver
executes the same algorithm against the data and checksum. If checksums do
not match, the sender knows to retransmit the data.
The parity check is binary: A stream of data has either even parity or odd
parity. If the number of 1s in the data is even, parity is even. If the number of
1s in the data is odd, the parity is odd. You can use xor to calculate this par-
ity. Here is a simple stand-alone test:
Xoring an even number of 1s will always result in an even parity (0). Xor-
ing an odd number of 1s will always result in an odd parity (1).
366 MATHEMATICS
The reason this works: Xoring is the same as adding two numbers, then
taking modulus of dividing by 2. Here is an extension of the truth table that
demonstrates this:
0 ^ 0 = 0 (0 + 0) % 2 = 0
Bit Manipulation
0 ^ 1 = 1 (0 + 1) % 2 = 1
1 ^ 0 = 1 (1 + 0) % 2 = 1
1 ^ 1 = 1 (1 + 1) % 2 = 0
Dividing mod 2 tells you whether a number is odd (1) or even (0). When
you add a string of binary digits together, only the 1 digits will contribute to
a sum. Thus, taking this sum modulus 2 will tell you whether the number of
1 digits is odd or even.
Take this one level further to calculate a checksum for any integer. The job
of the ParityChecker class is to calculate a checksum for a byte array of data.
The test shows how corrupting a single byte within a datum results in a dif-
ferent checksum.
package sis.util;
import junit.framework.*;
assertEquals(checksum, checker.checksum(data));
assertFalse(checksum == checker.checksum(data));
}
}
ParityChecker loops through all bytes of data, xoring each byte with a cu-
mulative checksum. In the test, I lined up the binary translation of each deci-
mal number (source1, source2, and source3). This allows you to see how the
checksum of 5 is a result of xoring the bits in each column.
BIT MANIPULATION 367
package sis.util;
A simple xor parity check will not catch all possible errors. More-complex
schemes involve adding a parity bit to each byte transmitted in addition to
the a parity byte at the end of all bytes transmitted. This provides a matrix
scheme that can also pinpoint the bytes in error.
Bit Shifting
Java provides three operators for shifting bits either left or right.
To shift bits left or right, and maintain the sign bit, use the bit shift left (<<)
or bit shift right (>>) operators.
A bit shift left moves each bit one position to the left. Leftmost bits are
lost. Rightmost bits are filled with zeroes.
For example, shifting the bit pattern 1011 one position to the left in a 4-bit
unsigned number would result in 0110. Some annotated Java examples:
// 101 = 5
assertEquals(10, 5 << 1); // 1010 = 10
assertEquals(20, 5 << 2); // 10100 = 20
assertEquals(40, 5 << 3); // 101000 = 40
assertEquals(20, 40 >> 1);
assertEquals(10, 40 >> 2);
// ... 1111 0110 = -10
assertEquals(-20, -10 << 1); // ... 1110 1100 = -20
assertEquals(-5, -10 >> 1); // ... 1111 1011 = -5
You’ll note that bit shifting left is the equivalent of multiplying by powers
of 2. Bit shifting right is the equivalent of dividing by powers of 2.
The unsigned bit shift right shifts all bits irrespective of the sign bit.
Rightmost bits are lost, and the leftmost bits (including the sign bit) are filled
with 0.
assertEquals(5, 10 >>> 1);
assertEquals(2147483643, -10 >>> 1);
// 1111_1111_1111_1111_1111_1111_1111_0110 = -10
// 0111_1111_1111_1111_1111_1111_1111_1011 = 2147483643
Note that an unsigned bit shift right always results in a positive number.
368 MATHEMATICS
BitSet
The Java API library includes the class java.util.BitSet. It encapsulates a vec-
tor of bits and grows as needed. BitSet objects are mutable—its individual
bits may be set or unset. You can bit-and, bit-or, or bit-xor one BitSet object
to another. You can also negate a BitSet. One of its few benefits is that it sup-
ports bit operations for numbers larger than the capacity of an int.
java.lang.Math
The class java.lang.Math provides a utility library of mathematical functions.
It also provides constant definitions Math.E, for the base of the natural log, and
Math.PI, to represent pi. These constants are double quantities that provide fif-
teen decimal places.
The Java API library also contains a class named java.lang.StrictMath,
which provides bit-for-bit adherence to accepted standard results for the
functions. For most purposes, Math is acceptable and also results in better
performance.
I summarize the functions provided by the Math class in the following
table. Refer to the Java API documentation for complete details on each
function.
pow x raised to y
import junit.framework.*;
// in util.Math:
package util;
The pow function is used to square the sides a and b. The hypotenuse method
then sums these squares and returns the square root of the sum using sqrt.
Pervasive use of the Math class gives you a justifiable reason for using the
static import facility introduced in Lesson 4. Code with a lot of Math
method calls otherwise becomes a nuisance to code and read.
370 MATHEMATICS
I’ve named the utility class Math, like the java.lang.Math class. While this
is a questionable practice (it may introduce unnecessary confusion), it
demonstrates that Java can disambiguate the two.
Numeric
Wrapper
Classes
Numeric Wrapper Classes
As you saw in Lesson 7, Java supplies a corresponding wrapper class for each
primitive type: Integer, Double, Float, Byte, Boolean, and so on. The primary
use of the wrapper class is to convert primitives to object form so that they
can be stored in collections.
The wrapper classes for the numeric types have additional uses. You
learned earlier in this lesson that each numeric wrapper class provides a
MIN_VALUE and MAX_VALUE constant. You also saw how the Float and Double classes
provide constants for Not a Number (NaN) and infinity.
Printable Representations
The class Integer provides static utility methods to produce printable repre-
sentations of an int in hex, octal, and binary form.
assertEquals("101", Integer.toBinaryString(5));
assertEquals("32", Integer.toHexString(50));
assertEquals("21", Integer.toOctalString(17));
Random Numbers
The class Math provides the method random to return a pseudorandom double
between 0.0 and 1.0. A number in this range may be all you need. The gener-
ated number is not truly random—the elements of a sequence of pseudoran-
dom numbers are only approximately independent from each other.4 For
most applications, this is sufficient.
The class java.util.Random is a more comprehensive solution for generat-
ing pseudorandom numbers. It produces pseudorandom sequences of booleans,
bytes, ints, longs, floats, Gaussians, or doubles. A pseudorandom sequence gener-
ator is not purely random; it is instead based upon an arithmetical algorithm.
You can create a Random instance with or without a seed. A seed is essen-
tially a unique identifier for a random sequence. Two Random objects cre-
ated with the same seed will produce the same sequence of values. If you
create a Random object without explicitly specifying a seed, the class uses the
system clock as the basis for the seed.5
You could create a simulation of coin flips through repeated use of the
nextBoolean method on Random. A true value would indicate heads and a false
4
http://en.wikipedia.org/wiki/Pseudo-random_number_generator.
5
Which means that if you construct two Random objects and the construction hap-
pens to execute in the same nanosecond, you get the same sequence in both. In earlier
versions of Java, the Random constructor used the current system time in millisec-
onds. This increased granularity significantly increased the likelihood of two Random
objects having the same sequence.
372 MATHEMATICS
value would indicate tails. The below test shows use of the seed value to pro-
duce two identical pseudorandom coin-flip sequences.
public void testCoinFlips() {
final long seed = 100L;
Random final int total = 10;
Numbers Random random1 = new Random(seed);
List<Boolean> flips1 = new ArrayList<Boolean>();
for (int i = 0; i < total; i++)
flips1.add(random1.nextBoolean());
assertEquals(flips1, flips2);
}
The methods nextInt, nextDouble, nextLong, and nextFloat work similarly. An addi-
tional version of nextInt takes as a parameter a maximum value, such that the
method always returns a number from 0 through the maximum value.
You must assign passwords to students so they can access their accounts
online. Passwords are eight characters long. Each character of the password
must fall in a certain character range.
package sis.util;
import junit.framework.*;
generator.setRandom(new MockRandom('C'));
assertEquals("CDEFGHIJ", generator.generatePassword());
}
}
The test method testGeneratePassword sets the random variable into the Password- Random
Numbers
Generator class to point to an instance of MockRandom. The MockRandom
class extends from the Random class. The Java API documentation explains
how to properly extend the Random class by overriding the method next(int
bits) to return a random number based on a bit sequence. All other methods
in the Random class are based on this method.
The mock implementation takes an int representing a starting character
value as a parameter to its constructor. It uses this to calculate an initial value
for the random sequence and stores it as i. The initial value is a number rela-
tive to the lowest valid character in the password. Its next(int bits) method
simply returns and then increments the value of i.
package sis.util;
import junit.framework.*;
package sis.util;
import java.util.*;
374 MATHEMATICS
Note that if the package-level setRandom method is not called, the random
instance variable keeps its initialized value of a legitimate java.util.Random
instance. Since MockRandom meets the contract of java.util.Random, you
can substitute an instance of MockRandom for Random for purposes of
testing.
The goal of testGeneratePassword is to prove that the PasswordGenerator class
can generate and return a random password. The test does not need to re-
prove the functionality inherent in the Random class, but the test does need
to prove that a PasswordGenerator object interacts with a Random instance
(or subclass instance) through its published interface, nextInt(int max). The test
must also prove that PasswordGenerator uses the nextInt return value prop-
erly. In this example, PasswordGenerator uses the nextInt return value to help
construct an 8-character String of random values.
Java supplies an additional random class, java.util.SecureRandom, as a
standards-based, cryptographically strong pseudorandom number gen-
erator.
EXERCISES 375
Random
PasswordGeneratorTest PasswordGenerator
+ nextInt(max: int)
# next(bits: int)
Exercises
MockRandom
# next(bits: int)
Exercises
1. Create a test demonstrating the immutability of a BigDecimal. Add a
second BigDecimal to the first and show that the first still has the
original value.
2. Create one BigDecimal using the value "10.00" and another using the
value "1". Show they are not equal. Use multiplication and scale to cre-
ate a new BigDecimal from the second so that it equals the first. Now
reverse the transformation, starting from 10.00, and get back to 1.
3. Show that 0.9 and 0.005 * 2.0 are not equal with floats. To what preci-
sion are they equal? With doubles?
4. Why won’t the following compile? What are two ways it can be fixed?
public class CompilerError {
float x = 0.01;
}
The second time, use the division (/) operator and the multiplication
operator if necessary, but not the % operator. Test against the sequence
of integers from 1 through 10.
9. Change the chess code to use casting where appropriate. Eliminate the
Exercises
need for the toChar method on the Board class.
10. Which of the following lines will compile correctly? Why?
float x = 1;
float x = 1.0;
float x = (int)1.0;
does a reasonable verification: Ensure that the list size remains the
same and that two numbers were swapped with each call to the
swapper.
20. Show that the next double is not the same between a Random seeded
Exercises
with 1 and a Random with no seed. Can you make a test that ab-
solutely proves that this is true?
21. Challenge: Swap two numbers without a temporary variable by using
the xor operator.
22. Challenge: Programmatically demonstrate the number of bits required
to store each integral type (char, byte, short, int, long). Hint: Use shift op-
erators. Remember that signed types require an extra bit to store the
sign.
This page intentionally left blank
Lesson 11
IO
Organization
In this lesson you will learn about the input-output (IO) facilities of Java. IO
refers to anything that transfers data to or from your application. IO in Java
is wonderfully complete, wonderfully complex, and reasonably consistent.
You might use Java’s IO capabilities to write reports to files or to read user
input from the console.
In this lesson, you will learn about:
• organization of the stream classes
• character streams versus byte streams
• the File class
• data streams
• redirecting System.in and System.out
• object streams
• random access files
• nested classes
Organization
The java.io package contains dozens of classes to manage input and output
using data streams, serialization, and the file system. Understanding the orga-
nization and naming strategy of the classes in the package will help you mini-
mize the otherwise overwhelming nature of doing IO in Java.
Java IO is based upon the use of streams. A stream is a sequence of data
that you can write to or read from. A stream may have a source, such as the
379
380 IO
Character Streams
In Lesson 3, you created the RosterReporter class to write a report of stu-
dents enrolled in a course session. You wrote the report to a string, then
printed the report using System.out. Here is the existing RosterReporter
class:
package sis.report;
import java.util.*;
import sis.studentinfo.*;
import static sis.report.ReportConstant.NEWLINE;
CHARACTER STREAMS 381
class RosterReporter {
static final String ROSTER_REPORT_HEADER =
"Student" + NEWLINE +
"———-" + NEWLINE;
static final String ROSTER_REPORT_FOOTER =
NEWLINE + "# students = ";
RosterReporter(Session session) {
this.session = session; Character
} Streams
String getReport() {
StringBuilder buffer = new StringBuilder();
writeHeader(buffer);
writeBody(buffer);
writeFooter(buffer);
return buffer.toString();
}
wouldn’t need to store the entire report in a large string buffer, something
that has the potential to cause memory problems.
You have been told to make the student information system flexi-
ble. Initially the system must be able to write reports to either the
console or to local files. To meet this requirement, you will first up-
date the RosterReporter class to write directly to a character stream.
First, you will need to update the test.
Character package sis.report;
Streams
import junit.framework.TestCase;
import sis.studentinfo.*;
import static sis.report.ReportConstant.NEWLINE;
import java.io.*;
session.enroll(new Student("A"));
session.enroll(new Student("B"));
assertEquals(
RosterReporter.ROSTER_REPORT_HEADER +
"A" + NEWLINE +
"B" + NEWLINE +
RosterReporter.ROSTER_REPORT_FOOTER + "2" +
NEWLINE, rosterReport);
}
}
To use Java’s IO classes, you must import the package java.io. Many IO
operations can generate an exception. You’ll need to declare that the test
method throws an IOException.
You want the RosterReporter to write its report to a Writer object pro-
vided by the client. The class java.io.Writer is the base abstraction for charac-
ter streams. For purposes of testing, you can create an object of the Writer
subclass StringWriter. When you send the message toString to a StringWriter, it
returns a String of all characters written to it.
Note the improvement to the design. Instead of asking the RosterReporter
object for a report (getReport), you are telling it to write a report (writeReport).
CHARACTER STREAMS 383
import java.util.*;
import sis.studentinfo.*;
import static sis.report.ReportConstant.NEWLINE; Character
import java.io.*; Streams
class RosterReporter {
static final String ROSTER_REPORT_HEADER =
"Student" + NEWLINE +
"———-" + NEWLINE;
static final String ROSTER_REPORT_FOOTER =
NEWLINE + "# students = ";
RosterReporter(Session session) {
this.session = session;
}
In writeHeader, writeBody, and writeFooter, you have replaced calls to the String-
Builder method append with calls to the Writer method write. Also, previously
384 IO
the code passed a StringBuilder from method to method. Now you have a
Writer instance variable. By not passing the same pervasive variable to every
method, you can eliminate some duplication.
Watch your tests pass, then make the following refactorings to take advan-
tage of Java String formatting.
package sis.report;
Character
Streams import java.util.*;
import sis.studentinfo.*;
import java.io.*;
class RosterReporter {
static final String ROSTER_REPORT_HEADER = "Student%n———-%n";
static final String ROSTER_REPORT_FOOTER = "%n# students = %d%n";
RosterReporter(Session session) {
this.session = session;
}
}
}
package sis.report;
import junit.framework.TestCase;
import sis.studentinfo.*;
import java.io.*;
session.enroll(new Student("A"));
session.enroll(new Student("B"));
assertEquals(
String.format(RosterReporter.ROSTER_REPORT_HEADER +
"A%n" +
"B%n" +
RosterReporter.ROSTER_REPORT_FOOTER, 2),
rosterReport) ;
}
}
Writing to a File
You will now need to update RosterReporter to be able to take a filename as
a parameter. The test will need to make sure that the report is properly writ-
ten to an operating system file.
First, you will refactor RosterReporterTest to create a setUp method and an
assertion method assertReportContents. By doing so, you will set the stage to
quickly add a new test, testFiledReport, that uses these common methods and
thus introduces no duplication.
Within assertReportContents, you can modify the assertion to obtain the num-
ber of expected students from the session object. You will need to change
getNumberOfStudents in Session from package to public.
The refactored RosterReporterTest:
package sis.report;
import junit.framework.TestCase;
import sis.studentinfo.*;
import java.io.*;
386 IO
session.enroll(new Student("A"));
Writing to a File session.enroll(new Student("B"));
}
BufferedReader reader =
new BufferedReader(new FileReader(filename));
while ((line = reader.readLine()) != null)
buffer.append(String.format(line + "%n"));
reader.close();
assertReportContents(buffer.toString());
}
WRITING TO A FILE 387
After calling writeReport, the test uses a BufferedReader to read the contents
of the report file into a buffer. The test then passes the buffer’s contents to the
assertReportContents method.
A BufferedReader is a Reader subclass that can wrap another reader. Re-
member that you use Reader objects to read from character streams. The test
constructs a BufferedReader with a FileReader as a parameter. A FileReader
is a character-based input stream that can read data from a file. You con-
struct a FileReader using a filename. Writing to a File
You could read the file’s contents by directly using a FileReader instead of
a BufferedReader. However, a BufferedReader is more efficient since it
buffers characters as it reads. In addition, BufferedReader supplies the
method readLine to help you simplify your code. The readLine method returns a
logical line of input from the wrapped stream, using the system property
"line.separator" to delineate lines.
The new method in RosterReporter:
The writeReport method creates a FileWriter using the filename passed to it.
It wraps the FileWriter in a BufferedWriter. The method then passes the
writer to the existing writeReport method (which takes a PrintWriter as a para-
meter).
A finally block ensures that the PrintWriter is always closed, regardless of
whether writeReport threw an exception or not. You must remember to close
file resources, otherwise you may encounter locked file problems. Also,
buffered information will not appear in the file until you close the Writer.
You can also use the Writer method flush to force the Writer to write its con-
tents to the destination.
Notice that you were able to add functionality to the existing Roster-
Reporter class without modifying a single line of its code. You simply added
a new method. Achieving this ideal happens more often if you strive to en-
sure that your code maintains an optimal design at all times. Prefer the use of
abstractions in your code.
388 IO
java.io.File
The File class is not a stream-based class. Instead of working with streams,
the File class provides you with an interface into the file and directory struc-
ture of your underlying file system. It contains a number of file-based utili-
ties, such as the ability to delete files and to create temporary files.
When writing tests that work with files, you want to ensure that you have
java.io.File
a clean environment for testing. You also want to ensure that you leave
things the way they were when the test completes. With respect to testFiled-
Report, this means that you need to delete any existing report file as the first
step in the test and delete the created report file as the last step in the test.
new RosterReporter(session).writeReport(filename);
BufferedReader reader =
new BufferedReader(new FileReader(filename));
while ((line = reader.readLine()) != null)
buffer.append(String.format(line + "%n"));
reader.close();
assertReportContents(buffer.toString());
}
finally {
delete(filename);
}
}
The delete method uses the File class to accomplish its goals. It creates a
File object by passing a filename to its constructor. It calls the exists method
to determine whether the file can be found in the file system. Finally, it calls
the delete method, which returns true if the file system successfully removed
the file, false otherwise. A file system might not be able to delete a file if it is
locked or has its read-only attribute set, among other reasons.
BYTE STREAMS AND CONVERSION 389
Category Methods
Referring to Reader and Writer abstract classes instead allows for quick
A Student User
redirecting to a different medium (e.g., files), and also makes for easier testing.
Interface
import junit.framework.*;
import java.io.*;
import java.util.*;
import sis.studentinfo.*;
assertEquals(
expectedOutput.toString(),
outputStream.toString());
assertStudents(ui.getAddedStudents());
}
The test creates two StringBuffers. One will hold expected output—what
the console application displays—and the other will hold the input that a
user (the test, in this case) types in. The setup method explicitly populates each
of these:
private void setup(StringBuffer expectedOutput, StringBuffer input) {
expectedOutput.append(StudentUI.MENU);
input.append(line(StudentUI.ADD_OPTION));
expectedOutput.append(StudentUI.NAME_PROMPT);
input.append(line(name));
expectedOutput.append(line(StudentUI.ADDED_MESSAGE));
expectedOutput.append(StudentUI.MENU);
input.append(line(StudentUI.QUIT_OPTION));
}
The setup method can be viewed as presenting a script to which the system
must adhere. The buffer appends are intertwined to emulate the interaction
between system output and user input. This natural flow should aid you in
understanding the script. First, the user interface presents the user with a
menu:
expectedOutput.append(StudentUI.MENU);
The user responds to the menu by first typing the option to add a student,
then pressing enter.
input.append(line(StudentUI.ADD_OPTION));
To verify the example user “script” coded in the setup method, assertStudents
must ensure that the StudentUI object added only one student and that this
student’s data (its name) is as expected.
The entire StudentUI implementation appears below. To build this class
test-first and very incrementally, you should start with an even simpler set of
unit tests. First, ensure that the menu is presented and that the user can im-
mediately quit the application. Then add functionality to support adding stu-
dents. Start with assertions against the user interface flow, then add the
assertion to ensure that a student object was created. A more complete test
than this would ensure that multiple students could be created.
package sis.ui;
import java.io.*;
import java.util.*;
import sis.studentinfo.*;
List<Student> getAddedStudents() {
return students;
}
students.add(new Student(name));
writeln(ADDED_MESSAGE);
}
have one user interface class, it’s fine. But if you have a dozen related UI
classes, each controlling a portion of the user interface, you’d be better off
constructing the console wrappers in a single class.
For this example, let’s make the main method as simple as possible. You can
do this by redirecting console input and output using the System methods
setIn and setOut instead of wrapping the input and output streams in buffered
streams. You must wrap the ByteArrayOutputStream in a PrintStream in
Testing the
order to call setOut.
Application
public void testCreateStudent() throws IOException {
StringBuffer expectedOutput = new StringBuffer();
StringBuffer input = new StringBuffer();
setup(expectedOutput, input);
byte[] buffer = input.toString().getBytes();
assertEquals(
expectedOutput.toString(),
outputStream.toString());
assertStudents(ui.getAddedStudents());
}
finally {
System.setIn(consoleIn);
System.setOut(consoleOut);
}
}
You want to make sure you reset System.in and System.out by using a try-
finally statement.
The new constructor for StudentUI must use an InputStreamReader to
wrap stdin in a BufferedReader and an OutputStreamWriter to wrap stdout
in a BufferedWriter.
public StudentUI() {
this.reader =
new BufferedReader(new InputStreamReader(System.in));
this.writer =
new BufferedWriter(new OutputStreamWriter(System.out));
}
DATA STREAMS 395
After demonstrating that the test passes, you can now write a main method
that kicks off the application.
There are two trains of thought on this. First, it is possible to write a test
against the main method, since you can call it like any other method (but you Data Streams
must supply an array of String objects that represent command-line argu-
ments):
The other testing philosophy is that the main method is virtually unbreak-
able. It is one line of code. You will certainly run the application from the
command line at least once to ensure it works. As long as the main method
does not change, it can’t break, in which case you do not necessarily need a
test against the main method.
The choice is yours as to whether to test main or not. Regardless, you
should strive to minimize the main method to a single line. Refactor code from
the main method into either static or instance-side methods. Create utility
classes to help you manage command-line arguments. Test the code you
move out of main.
I hope you’ve noticed that testing this simple console-based user interface
required a good amount of code. Were you to write more of the console ap-
plication, the work would simplify as you built utility methods and classes to
help both testing and production coding.
Data Streams
You can write Java primitive data directly to a DataOutputStream. DataOut-
putStream is an example of a filtered stream. A filtered stream wraps another
stream to either provide additional functionality or to alter the data along the
way. The base filtered stream classes are FilteredOutputStream, FilteredIn-
putStream, FilteredWriter, and FilteredReader.
The filter in DataOutputStream provides methods to output each Java
primitive type: writeBoolean, writeDouble, and so on. It also provides the writeUTF
method to output a String.
396 IO
CourseCatalog
The student information system requires a CourseCatalog class to
store a list of all available course sessions. CourseCatalog will be re-
sponsible for persisting basic course information (department, course
number, start date, and number of credits) to a file so that the application can
be restarted without any data loss.
CourseCatalog
The CourseCatalog provides a load method that reads all Session objects
from a DataOutputStream into a collection. It also provides a store method to
write the collection to a DataOutputStream.
package sis.studentinfo;
import junit.framework.*;
import java.util.*;
import java.io.*;
session1 =
CourseSession.create(
course1, DateUtil.createDate(1, 15, 2005));
session1.setNumberOfCredits(3);
session2 =
CourseSession.create(
course2, DateUtil.createDate(1, 17, 2005));
session2.setNumberOfCredits(5);
catalog.add(session1);
catalog.add(session2);
}
The test loads a couple courses into the catalog, calls the store method,
clears the catalog, and then calls the load method. It asserts that the catalog
contains the two courses initially inserted.
The code other than load and store in CourseCatalog is trivial:
package sis.studentinfo;
import java.util.*;
import java.io.*;
output =
new DataOutputStream(new FileOutputStream(filename));
output.writeInt(sessions.size());
for (Session session: sessions) {
output.writeLong(session.getStartDate().getTime());
output.writeInt(session.getNumberOfCredits());
output.writeUTF(session.getDepartment());
output.writeUTF(session.getNumber());
}
}
CourseCatalog finally {
output.close();
}
}
Both load and store methods ensure that the associated data streams are
closed by use of a finally block.
Advanced Streams
Piped Streams
Object Streams
You use piped streams for a safe I/O-based data communication channel between
different threads. Piped streams work in pairs: Data written to a piped output
stream is read from a piped input stream to which it is attached. The piped
stream implementations are PipedInputStream, PipedOutputStream, PipedReader,
and PipedWriter. Refer to Lesson 13 for more information on multithreading.
SequenceInputStream
You can use a SequenceInputStream to allow a collection of input sources to
act as a single input stream. The collection of sources is ordered; when one
source is fully read, it is closed and the next stream in the collection is opened
for reading.
Pushback Streams
The primary use of pushback streams (PushbackInputStream and Pushback-
Reader) is for lexical analysis programs such as a tokenizer for a compiler.
They allow data to be put back onto a stream as if it had not yet been read
by the stream.
StreamTokenizer
The primary use of StreamTokenizer is also in parsing applications. It acts
similarly to StringTokenizer. Instead of returning only strings from the under-
lying stream, however, a StreamTokenizer returns the type of the token in ad-
dition to the value. A token type may be a word, a number, an end-of-line
marker, or an end-of-file marker.
Object Streams
Java provides the capability to directly read and write objects from and to
streams. Java can write an object to an object output stream by virtue of seri-
alizing it. Java serializes an object by converting it into a sequence of bytes.
400 IO
The ability to serialize objects is the basis for Java’s RMI (Remote Method
Invocation) technology. RMI allows objects to communicate with other ob-
jects on remote systems as if they were local. RMI in turn provides the basis
for Java’s EJB (Enterprise Java Bean) technology for component-based com-
puting.
You write and read objects using the classes ObjectOutputStream and Ob-
jectInputStream. As a quick demonstration, the following code is a rewrite of
Object Streams
the the methods store and load in the CourseCatalog class. The modified code
uses object streams instead of data streams.
The throws clause on the test method signature must change, since the load
method now throws a ClassNotFoundException. Within the context of this
example, it doesn’t seem possible for a ClassNotFoundException to be gener-
ated. You store a List of Session objects and immediately read it back, and
both java.util.List and Session are known to your code. An exception could
be thrown, however, if another application with no access to your Session
class were to read the objects from the file.
When you run the test, you should receive an exception: Object Streams
java.io.NotSerializableException: studentinfo.CourseSession
When you mark the abstract superclass as serializable, all its subclasses
will also be serializable. The Serializable interface contains no method defini-
tions, so you need not do anything else to the Session class.
An interface that declares no methods is known as a marker interface.
You create marker interfaces to allow a developer to explicitly mark a class
for a specific use. You must positively designate a class as capable of being
serialized. The Serializable marker is intended as a safety mechanism—you
may want to prevent certain objects from being serialized for security
reasons.
Transient
Course sessions allow for enrollment of students. However, you don’t want
the course catalog to be cluttered with student objects. Suppose, though, that
a student has enrolled early, before the catalog was created. The setUp method
in CourseCatalogTest enrolls a student as an example:
402 IO
session1 =
CourseSession.create(
course1, DateUtil.createDate(1, 15, 2005));
session1.setNumberOfCredits(3);
catalog.add(session1);
catalog.add(session2);
}
The list of students will not be serialized in this example. Your tests will now
pass.
Your last execution of tests persisted an object stream to the file named
CourseCatalogTest.testAdd.txt. The object stream stored in this file contains
Object Streams
Session objects created using the older definition of Session without the name
field.
Then create an entirely new test class, studentinfo.SerializationTest:
package sis.studentinfo;
import junit.framework.*;
The test tries to load the persisted object stream. Execute only this test. Do
not execute your AllTests suite. You should receive an exception that looks
something like:
testLoadToNewVersion(studentinfo.SerializationTest)
java.io.InvalidClassException: studentinfo.Session;
local class incompatible:
stream classdesc serialVersionUID = 5771972560035839399,
local class serialVersionUID = 156127782215802147
You optionally specify the classpath, followed by the list of classes for
which you wish to generate a serialVersionUID.
Remove the name field from Session. Rebuild and rerun your entire test
suite. Add a serialVersionUID definition to Session. At the same time, add back
the name field.
abstract public class Session
implements Comparable<Session>,
Iterable<Student>,
java.io.Serializable {
public static final long serialVersionUID = 1L;
private String name;
...
Then run only SerializationTest. Even though you’ve added a new field,
the version ID is the same. Java will initialize the name field to its default value
of null. If you change the serialVersionUID to 2L and rerun the test, you will cause
the stream version (1) to be out of synch with the local class version (2).
of additional data, and they are already being persisted elsewhere. You can
traverse the collection of course sessions and persist only the unique identifier
for each student to the object stream.1 When you load this compacted collec-
tion, you can execute a lookup to retrieve the complete student object and
store it in the course session.
To accomplish this, you will define two methods in Session, writeObject and
readObject. These methods are hooks that the serialization mechanism calls
when reading and writing each object to the object stream. If you don’t sup- Object Streams
ply anything for these hooks, default serialization and deserialization takes
place.
First, change the test in CourseCatalogTest to ensure that the enrolled stu-
dent was properly persisted and restored.
Make sure that the students field in Session is marked as transient. Then code
the writeObject definition for Session:
1
We have a small school. We don’t admit anyone with the same last name as another
student, so you can use that as your unique identifier.
406 IO
The first line of writeObject calls the method defaultWriteObject on the stream.
This will write every nontransient field to the stream normally. Subsequently,
the code in writeObject first writes the number of students to the stream, then
loops through the list of students, writing each student’s last name to the
stream.
On the opposite end, readObject first calls defaultReadObject to load all nontran-
sient fields from the stream. It initializes the transient field students to a new
ArrayList of students. It reads the number of students into size and iterates
size times. Each iteration extracts a student’s last name from the stream. The
code looks up and retrieves a Student object using this last name and stores
the Student in the students collection.
In real life, the findByLastName method might involve sending a message to a
student directory object, which in turn retrieves the appropriate student from
a database or another serialization file. For demonstration purposes, you can
provide a simple implementation that will pass the test:
Serialization Approaches
For classes whose definitions are likely to change, dealing with serialization
version incompatibility issues can be a major headache. While it is possible to
load serialized objects from an older version, it is difficult. Your best tactics
include:
When you serialize an object, you are exporting its interface. Just as you
should keep interfaces as abstract and unlikely to change as possible, you
should do the same with serializable classes.
2
There is a bit of risk in not persisting the indexes as you add Students. You could
mitigate this risk by writing the data length within the data file itself as well. Doing
so would allow you to re-create the index file by traversing through the data file.
408 IO
DataFile
StudentDirectory
-dataFilename: String
-keyFilename: String
-db: RandomAccessFile
KeyFile
package sis.studentinfo;
import junit.framework.*;
import java.io.*;
package sis.studentinfo;
import java.io.*;
import sis.db.*;
sis.db.DataFileTest
package sis.db;
import junit.framework.*;
import java.io.*;
import sis.util.*;
db.add(ID2, testData2);
assertEquals(2, db.size()); sis.db.DataFile-
Test
assertTestDataEquals(testData1, (TestData)db.findBy(ID1));
assertTestDataEquals(testData2, (TestData)db.findBy(ID2));
}
db = DataFile.open(FILEBASE);
assertEquals(2, db.size());
assertTestDataEquals(testData1, (TestData)db.findBy(ID1));
assertTestDataEquals(testData2, (TestData)db.findBy(ID2));
db = DataFile.create(FILEBASE);
assertEquals(0, db.size());
}
DataFileTest shows that you create a new DataFile using the static factory
method create. The create method takes the name of the DataFile as its para-
meter.
The test also shows that you insert objects into a DataFile by using the add
method, which takes a unique key and associated object as parameters. To
retrieve objects, you send the message findBy, passing with it the unique id of
the object to be retrieved. If the object is not available, DataFile returns null.
Static Nested
Persistence is tested by closing a DataFile and creating a new instance
Classes and using the static factory method open.3 The distinction between open and create is
Inner Classes that create will delete the data file if it already exists, while open will reuse an
existing data file or create a new one if necessary.
Note that the object returned by the findBy method requires a cast! This
suggests that DataFile is a candidate for implementing as a parameterized
type. (See Lesson 14 for more information on parameterized types.)
3
Technically the test does not prove disk persistence. You could have implemented a
solution that stored objects in a static-side collection. However, the point of the test is
not to protect you from being dishonest and coding a foolish solution. The test in-
stead demonstrates the expected behavior. If you’re not convinced, however, there’s
nothing that prohibits you from writing a test to ensure that the object is actually
stored in a disk file. It’s just a lot more complex, and probably unnecessary.
SIS.DB.DATAFILE 413
inner class from external code, it’s a bit of trickery that I’m not going to
show you here—don’t do it!
Static nested classes, on the other hand, can be used by external code as
long as the access specifier is not private. You have already used the static
nested class Entry in order to iterate key-value pairs in a Map object. You re-
ferred to this class as Map.Entry, since you used it in a code context other
than Map.
So the first reason to declare a nested class as static is to allow other
classes to use it. You could declare the class as a top-level (i.e., non-nested)
class, but you may want to tightly couple it to the containing class. For ex-
ample, Map.Entry is tightly bound to Map, since it makes no sense for the
Entry class to exist in the absence of Map.
The second reason to declare a nested class as static is to allow it to be se-
rialized. You cannot serialize inner class objects, since they must have access
to the instance variables of the enclosing class. In order to make it work, the
serialization mechanism would have to capture the fields of the enclosing
class. Yuck.
Since you need to persist TestData objects, you must make the class serial-
izable. If TestData is to be a nested class, you must declare it as static.
sis.db.DataFile
package sis.db;
import java.util.*;
import java.io.*;
import sis.util.*;
414 IO
if (deleteFiles)
deleteFiles();
openFiles();
}
The DataFile class is the core part of the solution. It demonstrates use of a
RandomAccessFile object, which you store in the instance variable db (short
for database, one of the small number of abbreviations I use). You create a
RandomAccessFile object by passing in a File object and a String declaring
the mode in which you wish to access the RandomAccessFile.
416 IO
sis.db.KeyFileTest
package sis.db;
import junit.framework.*;
import java.io.*;
import java.util.*;
import sis.util.*;
SIS.DB.KEYFILETEST 417
assertEquals(1, keyFile.size());
assertTrue(keyFile.containsKey(KEY));
assertEquals(POSITION, keyFile.getPosition(KEY));
assertEquals(LENGTH, keyFile.getLength(KEY));
}
sis.db.KeyFile
package sis.db;
import java.util.*;
import java.io.*;
sis.db.KeyFile
class KeyFile {
private Map<String, EntryData> keys =
new HashMap<String, EntryData>();
private File file;
int size() {
return keys.size();
}
KeyFile stores the key information using a Map named keys. This Map ob-
ject maps the key to a serializable static nested class, EntryData, which con-
tains the data position and length. When closed, the KeyFile writes the entire
Map to the file by using an ObjectOutputStream. It loads the entire Map
when opened.
sis.util.IOUtilTest
package sis.util;
import junit.framework.*;
import java.io.*;
assertTrue(IOUtil.delete(FILENAME1));
TestUtil.assertGone(FILENAME1);
}
The most interesting aspect of IOUtilTest is that it contains four test meth-
ods, each testing the same IOUtil method delete. Each test proves a typical
scenario. There are probably many more tests possible. It’s up to you to de-
cide whether you have enough tests to give you the confidence you need in
your code.
sis.util.IOUtil
package sis.util;
import java.io.*;
The delete method uses varargs to allow you to delete multiple files in a
single method call. It returns true only if all files were successfully deleted.
sis.util.TestUtil
sis.util.TestUtil
package sis.util;
import junit.framework.*;
import java.io.*;
Only tests use code in the TestUtil class. Since TestUtil does not extend
from junit.framework.TestCase, it does not have instance access to any assert
methods. However, the class junit.framework.Assert defines the assert meth-
ods as class methods, allowing you to access them anywhere.
Other things you will need to do:
• Make the GradingStrategy implementation types and Student serializ-
able.
• Add an id field and associated getter/setter methods to the Student class.
• Update the AllTests suite classes.4
4
Having to remember to add the classes is asking for trouble. In Lesson 12, you’ll
learn how to use Java’s reflections capabilities to dynamically build test suites.
422 IO
Exercises
1. Create a test to write the text of this exercise to the file system. The
test should read the file back in and make assertions about the con-
tent. Ensure that you can run the test multiple times and have it pass.
Finally, make sure that there are no leftover files when the test fin-
ishes, even if an exception is thrown.
2. (hard) Create a timing test to prove that using a Buffered class is im-
portant for performance. The test can loop through various sizes of
EXERCISES 423
425
426 REFLECTION AND OTHER ADVANCED TOPICS
You might need to use mock objects if your code must interact with an ex-
ternal API. You typically have little control over the external code or the re-
sults an API returns. Writing tests against such code often results in breakage
due to changing results from the API. Also, the API might communicate with
an external resource that is not always available. Such an API introduces a
bad dependency that mocks can help you manage.1
Ensure that you have a dependency issue before introducing
mocks.
The student Account class needs to be able to handle transfers
from an associated bank account. To do so, it must interact with
ACH (automated clearing house) software from Jim Bob’s ACH
Mock Objects Shop. The ACH software publishes an API specification. Another
Revisited
bad dependency: We haven’t licensed or installed the actual software yet! But
you need to start coding now in order to be able to ship working software
soon after the actual code is licensed and delivered.
The test itself is small.
package sis.studentinfo;
import java.math.BigDecimal;
import junit.framework.*;
assertEquals(amount, account.getBalance());
}
}
1
[Langr2003].
THE JIM BOB ACH INTERFACE 427
// com.jimbob.ach.AchCredentials
package com.jimbob.ach;
428 REFLECTION AND OTHER ADVANCED TOPICS
// com.jimbob.ach.AchTransactionData
package com.jimbob.ach;
import java.math.BigDecimal;
// com.jimbob.ach.AchResponse
package com.jimbob.ach;
import java.util.*;
// com.jimbob.ach.AchStatus
package com.jimbob.ach;
For now, you are interested only in the Ach interface method named
issueDebit. You must send the issueDebit message to a JimBobAch object in order
to withdraw money from the bank account. The message takes an Ach-
Credentials object and an AchTransactionData object as parameters. Note
the questionable practice of exposing the instance variables directly in these
two classes. Unfortunately, when you deal with third-party vendor software,
you get what you get.
The issueDebit method returns an AchResponse object upon completion.
This response data indicates whether or not the debit succeeded by using an
AchStatus enum value.
THE MOCK CLASS 429
package sis.studentinfo;
import java.util.*;
import com.jimbob.ach.*;
import junit.framework.Assert;
}
public AchResponse voidSameDayTransaction(
AchCredentials credentials,
AchTransactionData data,
String traceCode) {
return null;
}
public AchResponse queryTransactionStatus(AchCredentials credentials,
AchTransactionData data, String traceCode) {
return null;
}
}
assertEquals(amount, account.getBalance());
}
MockAch JimBobAch
import java.math.BigDecimal;
import com.jimbob.ach.*;
credit(amount);
The Account }
Class
Implementation private AchCredentials createCredentials() {
AchCredentials credentials = new AchCredentials();
credentials.merchantId = "12355";
credentials.userName = "sismerc1920";
credentials.password = "pitseleh411";
return credentials;
}
Account allows clients to pass it an Ach reference that it then stores. In the
production system, your client code that constructs an Account will pass the
Account a JimBobAch object. In the test, your code passes a MockAch object
to the Account. In either case, Account doesn’t know and doesn’t care which
concrete type it is sending the issueDebit message to.
While testTransferFromBank now passes, it is difficult to see exactly why. The
mock class is in another source file, so to figure out what’s going on, you
must flip between the two class definitions. This isn’t totally unacceptable,
ANONYMOUS INNER CLASSES 433
but you can improve upon things. One solution would be to include the
mock definition as a nested class. Another would be to set up the mock di-
rectly within the test method, using an anonymous inner class.
Then terminate the mock Ach method definitions with a closing brace and
semicolon. The transformed test:3
public void testTransferFromBank() {
Ach mockAch = new Ach() {
public AchResponse issueDebit(
AchCredentials credentials, AchTransactionData data) {
Assert.assertTrue(
data.account.equals(AccountTest.ACCOUNT_NUMBER));
Assert.assertTrue(data.aba.equals(AccountTest.ABA));
3
You’ll need to import com.jimbob.ach and java.util to get this to compile.
434 REFLECTION AND OTHER ADVANCED TOPICS
Instance Initializers
In Lesson 4 you learned about static initialization blocks. Java allows you to use
instance initialization blocks, which are also known as instance initializers.
Within an anonymous inner class, you cannot create a constructor. Since the
anonymous inner class has no name, you wouldn’t be able to supply a name for its
constructor! Instead, you can use an instance initialization block.
Expirable t = new Expirable() {
private long then;
{
long now = System.currentTimeMillis();
then = now + 86400000;
}
Anonymous
Inner Classes
public boolean isExpired(Date date) {
return date.getTime() > then;
}
};
Instance initializers are rarely used within top-level classes. Prefer either con-
structor or field-level initialization. However, you might use instance initializers to
eliminate duplication if you have multiple constructors that must execute common
setup code.
String traceCode) {
return null;
}
public AchResponse issueCredit(AchCredentials credentials,
AchTransactionData data) {
return null;
}
public AchResponse voidSameDayTransaction(
AchCredentials credentials,
AchTransactionData data,
String traceCode) {
return null;
}
public AchResponse queryTransactionStatus (
AchCredentials credentials,
AchTransactionData data, String traceCode) {
return null;
}
};
account.setAch(mockAch);
assertEquals(amount, account.getBalance());
}
ADAPTERS 435
creates a reference named mockAch of the interface type Ach. The right-hand
side instantiates an Ach object using new. But Ach is an interface—how can
you instantiate an interface?
Java is allowing you to dynamically provide an implementation for the
Ach interface and at the same time create an instance with that implementa-
tion. You provide the implementation in a block of code immediately after
the constructor call (new Ach()) but before the semicolon that terminates the
statement.
Adapters
The implementation supplied in the block is of Ach type, but it has no spe-
cific class name. It is anonymous. You can send messages to this object, store
it, or pass it as a parameter, just as you might with an object of a named Ach
implementation such as MockAch.
Make sure that your tests pass, but don’t delete MockAch just yet.
As you’ve seen, anonymous inner classes can be useful when building
mocks for testing purposes. Swing applications (see Additional Lesson I) and
multithreading (see Lesson 13) often make heavy use of anonymous inner
classes.
Admittedly, it’s still a bit difficult to follow what’s going on in testTransfer-
FromBank. The test method is now long and cluttered. The next section on
adapters shows how to improve on things.
Adapters
Overusing anonymous inner classes can actually make code harder to follow.
Your goal should be to keep methods short, even test methods. The long
anonymous inner class implementation in testTransferFromBank means that you
will probably need to scroll in order to get the big picture of what the
method is doing.
You can create an interface adapter class that provides an empty imple-
mentation for the Ach interface. Modify the MockAch class to supply an
empty definition for the issueDebit method.
package sis.studentinfo;
import java.util.*;
import com.jimbob.ach.*;
import junit.framework.Assert;
436 REFLECTION AND OTHER ADVANCED TOPICS
account.setAch(mockAch);
ACCESSING VARIABLES FROM THE ENCLOSING CLASS 437
assertEquals(amount, account.getBalance());
}
You are still creating an anonymous inner class. An anonymous inner class
can either implement an interface or subclass another class. This code
demonstrates the latter. Here, you use the MockAch class as an adapter to
hide the methods that your mock doesn’t need to worry about. Now you can
include the relevant mock code in a reasonably brief test method.
Accessing
Variables from
Accessing Variables from the Enclosing Class the Enclosing
Class
You need an additional test, testFailedTransferFromBank, to define what
happens when the bank rejects the debit request. The test and mock
are going to be very similar to testTransferFromBank. The student’s bal-
ance should not change on a failed transfer, so the test assertion must change.
The status returned from the mock in the AchResponseData object must be
AchStatus.FAILURE.
Because the mocks will be almost exactly the same, you want a way to cre-
ate both of them without introducing duplicate code. You want to be able to
phrase the new test as follows:4
public void testFailedTransferFromBank() {
account.setAch(createMockAch(AchStatus.FAILURE));
final BigDecimal amount = new BigDecimal("50.00");
account.transferFromBank(amount);
assertEquals(new BigDecimal("0.00"), account.getBalance());
}
Anonymous inner class objects are just objects. They can be created in a sep-
arate method and returned like any other object.
4
You may also want the Account code to throw an exception if the ACH debit fails. I
avoided the exception here for simplicity reasons.
438 REFLECTION AND OTHER ADVANCED TOPICS
However, this code won’t compile. You will see the message:
local variable status is accessed from within inner class; needs to be declared final
Anonymous inner classes are inner classes. By the definition you learned in
Lesson 11, an inner classes has access to the instance variables (and methods)
of the enclosing class.
An anonymous inner class does not have access to local variables. The
status parameter is analogous to a local variable because it is accessible only
to code in the createMockAch method.
According to the compiler error, however, you can get around this limita-
tion by declaring the status parameter as final.
private Ach createMockAch(final AchStatus status)
After you do this, your code should compile but your tests will not pass.
TRADEOFFS 439
Why must you define the variable as final? Remember that by declaring a
variable as final, you are declaring that its value cannot be changed after it
has been set.
An instance of an anonymous inner class can exist outside of the method
in which it is declared. In our example, the createMockAch method instantiates a
new anonymous inner class and returns it from the method it will be used by
the calling test methods
Local variables do not exist once a method has finished executing! Nor do
parameters—all parameters to methods are copies of the arguments passed
by the calling code.5 If an anonymous inner class was able to access local
variables, the value of the local variable could be destroyed by the time the
code in the anonymous inner class actually executed. This would, of course, Tradeoffs
be a bad thing.
To get your tests to pass, insert the code in Account so that you credit only
successful transfers.
public void transferFromBank(BigDecimal amount) {
AchCredentials credentials = createCredentials();
AchTransactionData data = createData(amount);
Ach ach = getAch();
AchResponse achResponse = ach.issueDebit(credentials, data);
if (achResponse.status == AchStatus.SUCCESS)
credit(amount);
}
Tradeoffs
Earlier, I mentioned that it is a good thing to be able to see the mock behav-
ior right in the test itself. Then I had you extract the mock definition to a sep-
arate method in order to eliminate the duplication that was impending with
the addition of another test method that needed a similar mock. This was at
a cost of some expressiveness, although it’s reasonably clear what the code
5
For a further discussion, see Additional Lesson III, which talks about call by value.
440 REFLECTION AND OTHER ADVANCED TOPICS
account.setAch(createMockAch(AchStatus.SUCCESS));
is doing.
Which is worse? Duplication or code that is hard to follow? The answer
isn’t always clear cut. The question often engenders debate. From my per-
spective, duplicate code is usually more troublesome than difficult-to-read
code. You don’t often need to choose one over the other, but when you do,
eliminating duplication is a safer route. By eliminating duplicate code, you
reduce the cost of maintenance efforts—you avoid having to make changes in
more than one place. You also minimize the risk of making a change in one
place but forgetting to make it in another place.
Reflection
Reflection
You’ve seen how the use of the Object method getClass allows you to deter-
mine the type of an object at runtime. This is known as reflective capabil-
ity—the ability of code to examine information about the code itself; that is,
to reflect on itself. Another term used for reflection capabilities is metadata.
JUnit uses reflection heavily to gather the information it needs to be able
to run your tests. You supply a test class file to JUnit to be tested. JUnit uses
reflection to determine whether or not your class file inherits from
junit.framework.TestCase, rejecting it if it does not. JUnit also uses reflection
to obtain a list of methods in a test class. It then iterates these methods, exe-
cuting only those that pass its criteria for being a test method:
• The method name must start with the word “test,” in lowercase letters.
• The return type of the method must be void.
• The method must not take any parameters.
So far, you have been telling JUnit which classes you want tested. This al-
lows you to run only the tests that you specify. A problem with this approach
is that it is easy to forget to add your test class to a suite. You might end up
skipping critical tests!
Another approach is to have Java scan through all the classes in your
classpath, gather the classes that are legitimate test classes, and execute only
those. The benefit is that you don’t have to maintain the suites in code. No
tests will be left behind. The only downside is that there may be tests, such as
performance tests, that you don’t want run all the time. However, you can
write additional code to bypass such tests if necessary.
USING JUNIT CODE 441
import junit.framework.*;
import java.util.*;
The interesting part of the test is that it binds the list to the String type—
not the Class type. When you look at the source for ClassPathTestCollector,
you will note that it returns an Enumeration of Strings, each representing the
class name. We’ll go along with that for now and see if it causes any prob-
lems later.
package sis.testing;
import java.util.*;
import junit.runner.*;
import junit.framework.*;
The Class Class The query method isTestClass concerns itself only with the class filenames,
not with the structure of the classes themselves. The conditional states that if
a filename ends in a .class extension, does not contain a $ (i.e., it does not
represent a nested class), and contains the text “Test,” then it is a test class.
You might recognize that this definition could cause problems: What if a
file with a .class extension is not a valid class file? What if the class name
contains the text “Test” but is not a test class? The last condition is highly
likely. Your own code for the student information system contains the class
util.TestUtil, which is not a TestCase subclass.
You’re not going to worry about these problems (yet) since your test
doesn’t (yet) concern itself with any of these cases. For now, your implementa-
tion code in getTestClassNames overrides isTestClass and simply calls the superclass
method to use its existing behavior. This is unnecessary, but it acts as a place-
holder and reminder when you are ready to take care of the other concerns.
The last line of gatherTestClassNames sends the message collectTests to the Class-
PathTestCollector. Somewhere along the way, collectTests indirectly calls isTest-
Class. The return type of collectTests is java.util.Enumeration. You can use the
java.util.Collections utility method list to transform the Enumeration into an
ArrayList. You will get an unchecked warning, since ClassPathTestCollector
uses raw collections—collections not bound to a specific type.
package sis.testing.testclasses;
public class NotATestClass {}
}
};
return Collections.list(collector.collectTests());
}
6
Note use of the variable name klass to represent a Class object. You can’t use class,
since Java reserves it for defining classes only. Some developers use the single charac-
ter c (not recommended), others clazz.
444 REFLECTION AND OTHER ADVANCED TOPICS
are three steps involved. The following descriptions correspond to the com-
mented lines of code in gatherTestClassNames.
1. Translate the directory-based filename into a class name, for example
from “testing/testclasses/NotATestClass” to “testing.testclasses.Not-
ATestClass.” The ClassPathTestCollector method classNameFromFile ac-
complishes this task.
2. Create a Class object from the class name. In createClass, the static
method call of forName, defined on Class, does this. The forName method
throws a ClassNotFoundException if the String object you pass to it
does not represent a class that can be loaded by Java. For now, you
can return null from createClass if this occurs, but you will need to write
Building the a test to cover this possibility.
Suite
3. Determine whether the class is a subclass of TestCase. To do so, you
will use a method on the class java.lang.Class, which provides meta-
data about a class definition. The Class method isAssignableFrom takes as
a parameter a Class object and returns true if it is possible to assign an
instance of the parameter (klass, in the above code) to a reference of
the receiving class type (junit.framework.TestCase).
Take a brief look at the Java API documentation for java.lang.Class. It
contains methods such as getMethods, getConstructors, and getFields that you can
use to derive most of the information you need about a compiled Java class.
I used a mock instead of letting the suite method gather all classes nor-
mally. Otherwise, gatherTestClassNames would return the entire list of classes on
the classpath. This list would include all other tests in the student informa-
tion system. You would have no definitive way to prove that the suite
method was doing its job.
Determining whether or not a class is included in a TestSuite is not as triv-
ial as you might expect. A test suite can either contain test case classes or
other test suites. Figure 12.2 shows this design, known as a composite.7 The
filled-in diamond represents a composition relationship between TestSuite
and Test. A TestSuite is composed of Test objects.
To determine if a class is contained somewhere within a suite, you must
traverse the entire hierarchy of suites. The contains method here uses recur- Building the
Suite
sion, since contains calls itself when encountering a suite within the current
suite.
public boolean contains(TestSuite suite, Class testClass) {
List testClasses = Collections.list(suite.tests());
for (Object object: testClasses) {
if (object.getClass() == TestSuite.class)
if (contains((TestSuite)object, testClass))
return true;
if (object.getClass() == testClass)
return true;
}
return false;
}
All that is left is for you to create a TestRunner class. TestRunner will use
the SuiteBuilder to construct a suite. It will then execute the Swing test run-
ner using this suite. There are no tests—you’ll exercise the code as part of
TestSuite
addTest(:Test) TestCase
addTestMethod(:Method, …)
addTestClass(:Class)
7
[Gamma1995].
446 REFLECTION AND OTHER ADVANCED TOPICS
continually running JUnit. Writing tests for this code would be possible but
difficult. The way you constructed SuiteBuilder, however, means that the
TestRunner code is so small, it’s almost breakproof.
package sis.testing;
When you run all the tests, however, you get about half a dozen failures.
Class Modifiers
The problem is that SessionTest, an abstract class, contains a number of defi-
nitions for test methods. The methods are intended to be executed as part of
SessionTest subclasses; thus, you did not previously add SessionTest to a
suite. You need to modify SuiteBuilder to ignore abstract classes.
Create a test class that is abstract and extends from TestCase.
package sis.testing.testclasses;
assertFalse(classes.contains("testing.testclasses.NotATestClass"));
assertFalse(
classes.contains("testing.testclasses.AbstractTestClass"));
}
Run the test and ensure that it fails. Only then should you go on to correct
the code in SuiteBuilder. You are following a testing pattern: If you release
code that fails either in production or in an acceptance test (a customer-
defined test that exercises code from an end user’s standpoint), it means that
it was inadequately unit-tested. Your job is to add the missing test code and
ensure that it fails for the same reason that the acceptance test failed8.
Never depend solely on one level of testing. Ensure you have a
layer above unit testing to ferret out “holes” in your unit tests. Class Modifiers
Now that you’ve seen the test fail, you can add code to Suite-
Builder that corrects the defect:
public List<String> gatherTestClassNames() {
TestCollector collector = new ClassPathTestCollector() {
public boolean isTestClass(String classFileName) {
if (!super.isTestClass(classFileName))
return false;
String className = classNameFromFile(classFileName);
Class klass = createClass(className);
return
TestCase.class.isAssignableFrom(klass) &&
isConcrete(klass);
}
};
return Collections.list(collector.collectTests());
}
8
[Jeffries2001], p. 163.
9
Methods and fields also have modifiers and use the same int-flag construct.
448 REFLECTION AND OTHER ADVANCED TOPICS
Dynamic Proxy
J2SE version 1.3 introduced a new class, java.lang.reflect.Proxy, that allows
you to construct dynamic proxy classes. A dynamic proxy class allows you to
Dynamic Proxy dynamically—at runtime—implement one or more interfaces.
The proxy pattern is one of the more useful patterns identified in the book
Design Patterns.10 A proxy is a stand-in; a proxy object stands in for a real
object. In a proxy pattern implementation, client objects think they are inter-
acting with the real object but are actually interacting with a proxy.
Figure 12.3 shows the proxy pattern in the context of distributed object
communication. A ServiceImplementation class resides on a separate ma-
chine in a separate process space from the Client class. In order for a Client
to interact with a ServiceImplementation, some low-level communications
must take place. Suppose Client must send the message submitOrder to a Ser-
viceImplementation. This Java message send must be translated to a data
stream that can be transmitted over the wire. On the server side, the data
ClientSkeletonProxy
ServerStub
10
[Gamma1995].
A SECURE ACCOUNT CLASS 449
11
EJB is a part of the J2EE (Java 2 Enterprise Edition) platform.
450 REFLECTION AND OTHER ADVANCED TOPICS
any method defined on Account. A client with read-only access can use only
methods that don’t change an account’s state.
You could add code to the Account class, passing in a user’s classification
when the account is created. To each restricted method, you would add code
that would check the user classification and throw an exception if the user
did not have the proper access rights. Security-related code would quickly
clutter your Account class, obscuring its business logic. You would be violat-
ing the Single-Responsibility Principle!12
Instead, you will externalize the security restrictions into a proxy class.
The Account class itself will remain almost completely untouched! We are
going for the open-closed principle13 here—building new functionality by
A Secure adding new code, not by modifying existing code.
Account Class
The use of proxies often calls for factories. The UML diagram in Figure
12.4 shows the complete solution for the security proxy, including the use of
the class AccountFactory to return an instance of a SecureProxy. The client
can think that it is interacting with a real Account, but it is instead interact-
ing with a proxy that is able to respond to the same messages.
balance: BigDecimal
bankAba: String
<< interface >> bankAccountNumber: String
InvocationHandler bankAccountType: Account.BankAccountType
via Proxy
AccountFactory
creates indirectly
SecureProxy Account
<< static >> create(:Permission): Accountable
throws
PermissionException
12
[Martin2003], p. 95.
13
[Martin2003], p. 99.
BUILDING THE SECURE ACCOUNT SOLUTION 451
The AccountFactory class uses the dynamic proxy class Proxy to create
the SecureProxy object. SecureProxy does not directly implement Account-
able; instead, the Proxy class sets SecureProxy up to capture all incoming
messages and redirect each to the InvocationHandler interface method
invoke.
import java.math.*;
import java.util.*;
import java.lang.reflect.*;
import junit.framework.*;
import sis.security.*;
The test demonstrates some additional features of the Java reflection API.
BUILDING THE SECURE ACCOUNT SOLUTION 453
In the test, you create two collections, one for read-only methods and one
for update methods. You populate both read-only and update collections in
the setUp method with java.lang.reflect.Method objects. Method objects repre-
sent the individual methods defined within a Java class and contain informa-
tion including the name, the parameters, the return type, and the modifiers
(e.g., static and final) of the method. Most important, once you have a
Method object and an object of the class on which the method is defined,
you can dynamically execute that method.
You obtain Method objects by sending various messages to a Class object.
One way is to get an array of all methods by sending getDeclaredMethods to a
Class. The class will return all methods directly declared within the class.
You can also attempt to retrieve a specific method by sending the Class the Building the
Secure Account
message getDeclaredMethod, passing along with it the name and the list of para-
Solution
meter types. In the above code, the line:
Accountable.class.getDeclaredMethod(name, parmClass)
package sis.security;
You derive the Accountable interface by extracting all public method sig-
natures from the Account class:
package sis.studentinfo;
import java.math.*;
You’ll need to modify the Account class definition to declare that it imple-
ments this interface:
454 REFLECTION AND OTHER ADVANCED TOPICS
To execute a method, you send a Method object the invoke message, passing
along with it the object on which to invoke the method plus an Object array
of parameters. Here, you use the utility method nullParmsFor to construct an
array of all null values. It doesn’t matter what you pass the methods. Even if a
method generates a NullPointerException or some other sort of exception,
the test concerns itself only with PermissionException:
package sis.security;
The method verifyException expects the invoke message send to generate an In-
vocationTargetException, not a PermissionException. If the underlying
method called by invoke throws an exception, invoke wraps it in an Invocation-
TargetException. You must call getCause on the InvocationTargetException to
extract the original wrapped exception object.
The use of reflection in the test was not necessary. If this was not a lesson
on reflection, I might have had you implement the test in some other nondy-
namic manner.
The job of the AccountFactory class is to create instances of classes that
implement the Accountable interface.
package sis.studentinfo;
import java.lang.reflect.*;
BUILDING THE SECURE ACCOUNT SOLUTION 455
import sis.security.*;
return (Accountable)Proxy.newProxyInstance(
Accountable.class.getClassLoader(),
new Class[] { Accountable.class },
secureAccount);
}
}
The use of a factory means that the client is isolated from the fact that a
security proxy even exists. The client can request an account and, based on
the Permission enum passed, AccountFactory creates either a real Account
object (Permission.UPDATE) or a dynamic proxy object (Permission.READ_ONLY). The
work of creating the dynamic proxy appears in the method createSecuredAccount.
The class SecureProxy is the dynamic proxy object that you will construct.
It could act as a secure proxy for any target class. To construct a Secure-
Proxy, you pass it the target object and a list of the methods that must be se-
cured. (The list of methods could easily come from a database lookup. A
security administrator could populate the contents of the database through
another part of the SIS application.)
The second statement in createSecuredAccount is the way that you set up the Se-
cureProxy object to act as a dynamic proxy. It’s an ugly bit of code, so I’ll re-
peat it here with reference numbers:
return (Accountable)Proxy.newProxyInstance( // 1
Accountable.class.getClassLoader(), // 2
new Class[] { Accountable.class }, // 3
secureAccount); // 4
456 REFLECTION AND OTHER ADVANCED TOPICS
Line 1 invokes the Proxy factory method newProxyInstance. This method is not
parameterized, thus it returns an Object. You must cast the return value to an
Accountable interface reference.
The first parameter to newProxyInstance (line 2) requires the class loader for
the interface class. A class loader reads a stream of bytes representing a Java
compilation unit from some source location. Java contains a default class
loader, which reads class files from a disc file, but you can create custom class
loaders that read class files directly from a database or from a remote source,
such as the internet. In most cases you will want to call the method getClass-
Loader on the Class object itself; this method returns the class loader that origi-
nally loaded the class.
The The second parameter (line 3) is an array of interface types for which you
SecureProxy
want to create a dynamic proxy. Behind the scenes, Java will use this list to
Class
dynamically construct an object that implements all of the interfaces.
The type of the final parameter (line 4) is InvocationHandler, an interface
that contains a single method that your dynamic proxy class must implement
in order to intercept incoming messages. You pass your proxy object as this
third parameter.
package sis.security;
import java.lang.reflect.*;
import junit.framework.*;
};
proxy = new SecureProxy(object, secureMethodName);
}
The test testSecureMethod uses the expected exception testing idiom, looking
for a PermissionException. It also ensures that the secure method was never
called. The test testInsecureMethod ensures that the insecure method was called.
458 REFLECTION AND OTHER ADVANCED TOPICS
package sis.security;
import java.lang.reflect.*;
import java.util.*;
SecureProxy stores the target object and the list of methods to be secured
in its constructor. It implements the sole method in InvocationHandler, invoke.
Any messages sent to the proxy get routed to the invoke method. As you saw
in the test, the invoke method takes three parameters: the proxy object itself,
the method being called, and the parameters to the method.14 It’s up to you
to interpret the incoming information.
In SecureProxy, your interpretation is to look up the method name to see
if it is in the list of secure methods. If so, you throw a PermissionException.
If not, you delegate the message to the target by calling invoke on the incoming
Method object.
That’s it!! Your tests should pass.
14
If it has not been apparent until now, primitive parameters are autoboxed into ob-
ject references.
EXERCISES 459
Exercises
1. Use an anonymous inner class of type Comparable to sort the chess-
board positions that you created in an earlier exercise.
2. When you debug, you will often find that a toString method doesn’t
provide you with enough information. At times like this, it may be
handy to have a utility method to expose, or “dump,” information
that is more internal.
Create an object-dumper utility class that takes an object and lists
every field name on the object and the current dump of those objects,
recursively, using a hierarchical output format. Do not traverse into
460 REFLECTION AND OTHER ADVANCED TOPICS
classes from java or javax packages. The utility should be able to display
private fields. Mark static fields as such. For simplicity’s sake, ignore
fields from any superclass.
3. Yet another clone exercise: Implement another version of the poor
man’s clone, this time using reflection operations. Obtain a Construc-
tor object from the source object’s class. Call newInstance to create the
new object and copy each field’s values from the source object to the
new object. The class being cloned will need to provide a no-argument
constructor. Build support for a shallow copy only.
4. Create a Proxy class that delegates to the original object for every
method it defines, except for toString. When the proxy receives the
Exercises toString message, it delegates to the object dumper instead. The target
object will need to implement an interface type that defines toString,
since proxies work off an interface.
Lesson 13
Multithreading
This chapter presents the most difficult core Java technology to understand
and master: multithreading. So far, you have been writing code to run in a
single thread of execution. It runs serially, start to finish. You may have the
need, however, to multithread, or execute multiple passages of code simulta-
neously. Multithreading
You will learn about:
461
462 MULTITHREADING
Multithreading
Many multithreading needs are related to building responsive user interfaces.
For example, most word processing applications contain an “autosave” func-
tion. You can configure the word processor to save your documents every x
minutes. The autosave executes automatically on schedule without needing
your intervention. A save may take several seconds to execute, yet you can
continue work without interruption.
The word processor code manages at least two threads. One thread, often
referred to as the foreground thread, is managing your direct interaction with
the word processor. Any typing, for example, is captured by code executing
in the foreground thread. Meanwhile, a second, background thread is check-
ing the clock from time to time. Once the configured number of minutes has
elapsed, the second thread executes the code in the save function.
Search Server
On a multiple-processor machine, multiple threads can actually run simul-
taneously, each thread on a separate processor. On a single-processor ma-
chine, threads each get a little slice of time from the processor (which can
usually only execute one thing at a time), making the threads appear to exe-
cute simultaneously.
Learning to write code that runs in a separate thread is fairly simple. The
challenge is when multiple threads need to share the same resource. If you are
not careful, multithreaded solutions can generate incorrect answers or freeze
up your application. Testing multithreaded solutions is likewise complex.
Search Server
A server class may need to handle a large number of incoming requests. If it
takes more than a few milliseconds to handle each request, clients who make
the request may have to wait longer than an acceptable period of time while
the server works on other incoming requests. A better solution is to have the
server store each incoming request as a search on a queue. Another thread
can then take searches from the queue in the order they arrived and process
them one at a time. This is known as the active object pattern;1 it decouples
the method request from the actual execution of the method. See Figure 13.1.
All relevant search information is translated into a command object for later
execution.
1
[Lavender1996].
THE SEARCH CLASS 463
Thread
run()
Search
Client url
searchString
You will build a simple web search server. The server will take a
search object consisting of a URL and a search string as a request. As
it searches the URL, the server will populate the search object with
the number of times the string was found in the document at the URL.
Note: If you’re not connected to the Internet, the search test will not exe-
cute properly. This is corrected in short time—the section entitled Less De-
pendent Testing shows how to modify the test to execute against local URLs.
import junit.framework.TestCase;
import java.io.*;
464 MULTITHREADING
The implementation a:
package sis.search;
import java.net.*;
import java.io.*;
import sis.util.*;
import junit.framework.*;
// StringUtil.java
package sis.util;
URLs to file URLs.2 This also means you can write out test HTML files to
control the content of the “web” pages searched.
package sis.search;
import junit.framework.TestCase;
import java.io.*;
import java.util.*;
import sis.util.*;
You will need the LineWriter utility; here is the test and production source.
// LineWriterTest.java
package sis.util;
import junit.framework.*;
import java.io.*;
2
Another solution would involve installing a local web server, such as Tomcat.
468 MULTITHREADING
assertNull(reader.readLine());
}
finally {
if (reader != null)
reader.close();
}
}
finally {
TestUtil.delete(file);
}
}
}
// LineWriter.java
package sis.util;
import java.io.*;
The good part is that the changes to SearchTest are not very invasive. In
fact, none of the test methods need change. You are adding setup and tear-
down methods to write HTML to a local file and subsequently delete that file
Initialization Expressions
The method searchUrl contains the code:
String line;
while ((line = reader.readLine()) != null)
The parentheses should help you understand this code. First, the result of reader.read-
Line() is assigned to the line reference variable. The value of the line reference is com-
pared to null to determine whether or not the while loop should terminate.
LESS DEPENDENT TESTING 469
each time a test completes. You are also changing the URL to use the file pro-
tocol instead of the http protocol.
The bad part is that the Search class must change. To obtain an Input-
Stream from a URL with a file protocol, you must extract the path infor-
mation from the URL and use it to open the stream as a FileInputStream.
It’s not a significant change. The minor downside is that you now have a
bit of code in your production system that will probably only be used by
the test.
One other thing you want to consider is that you are no longer exercising
the portion of of the code that deals with an http URL. The best approach is
to ensure that you have adequate coverage at an acceptance test3 level. Ac-
ceptance tests provide a level of testing above unit testing—they test the sys-
tem from an end user’s standpoint. They are executed against as live a system
as possible and should not use mocks. For the search application, you would
certainly execute acceptance tests that used live web URLs.
3
There are many different nomenclatures for different types of testing. Acceptance
tests are the tests that signify that the system meets the acceptance criteria of the cus-
tomer to whom it’s delivered. You may hear these tests referred to as customer tests.
470 MULTITHREADING
The Server
ServerTest:
package sis.search;
import junit.framework.*;
import sis.util.*;
package sis.search;
The waitForResults method executes a simple loop. Each time through, the
body of the loop pauses for a millisecond, then makes a quick calculation of
elapsed time to see if it’s passed the timeout limit. The pause is effected by a
call to the static Thread method sleep. The sleep method takes a number of mil-
liseconds and idles the currently executing thread for that amount of time.4
Since sleep can throw a checked exception of type InterruptedException,
you can choose to enclose it in a try-catch block. It is possible for one thread
to interrupt another, which is what would generate the exception. But thread
interruptions are usually only by design, so the code here shows one of the
rare cases where it’s acceptable to ignore the exception and provide an empty
catch block.
The looping mechanism used in waitForResults is adequate at best. The
wait/notify technique, discussed later in this lesson (see Wait/Notify), pro-
vides the best general-purpose mechanism of waiting for a condition to occur.
4
The thread scheduler will wait at least the specified amount of time and possibly a
bit longer.
CREATING AND RUNNING THREADS 473
package sis.search;
import java.util.*;
Creating and The Server class defines two fields: a ResultsListener reference and a
Running LinkedList of Search objects, named queue. A flaw exists in the declaration of
Threads the queue reference—it is not “thread safe”! For now, your test will likely ex-
ecute successfully, but the flaw could cause your application to fail. In the
section Synchronized Collections in this chapter, you will learn about this
thread safety issue and how to correct it.
The java.util.LinkedList class implements the List interface. Instead of
storing elements in a contiguous block of memory, as in an ArrayList, a
linked list allocates a new block of memory for each element you add to it.
The individual blocks of memory thus will be scattered in memory space;
each block contains a link to the next block in the list. See Figure 13.2 for a
conceptual memory picture of how a linked list works. For collections that
require frequent removals or insertions at points other than the end of the
list, a LinkedList will provide better performance characteristics than an
ArrayList.
The queue reference stores the incoming search requests. The linked list in
this case acts as a queue data structure. A queue is also known as a first-in,
first-out (FIFO) list: When you ask a queue to remove elements, it removes
the oldest item in the list first. The add method in Server takes a Search para-
meter and adds it to the end of the queue. So in order for the LinkedList to
act as a queue, you must remove Search objects from the beginning of the
list.
The constructor of Server kicks off the second thread (often called a
worker or background thread) by calling start. The run method is invoked at
this point. The run method in Server is an infinite loop—it will keep executing
until some other code explicitly terminates the thread (see Stopping Threads
CREATING AND RUNNING THREADS 475
memory
address
0x0000
0x0100 data
:Data
next: 0x0600
0x0200
0x0300
data :Data
head next: 0x0100
0x0400
0x0500
Creating and
Running
0x0600 Threads
data
:Data
next: null
creates start()
search:Search
add(search:Search)
add(search: Search)
run()
search := remove(0)
execute
executed(search: Search)
Creating and Figure 13.3 Sequence Diagram for the Active Object Search
Running
Threads
find that sequence diagrams can be a very effective means of communicating
how a system is wired together with respect to various uses.5
In a sequence diagram, you show object boxes instead of class boxes. The
dashed line that emanates vertically downward from each box represents the
object’s lifeline. An “X” at the bottom of the object lifeline indicates its ter-
mination. The Search object in Figure 13.3 disappears when it is notified that
a search has been completed; thus, its lifeline is capped off with an “X.”
You represent a message send using a directed line from the sending object’s
lifeline to the receiver. Messages flow in order from top to bottom. In Figure
13.3, a Client object sends the first message, creates. This special message shows
that the Client object is responsible for creation of a Search object. Note that
the Search object starts farther down; it is not in existence until this point.
After the first message send, the Client sends add(Search) to the Server object.
This message send is conceptually asynchronous—the Client need not wait
for any return information from the Server object.6 You use a half-arrowhead
to represent asynchronous message sends. When it receives this add message,
the Server’s add method passes the Search object off to the queue. You repre-
sent the execution lifetime of the add method at the Server object using an ac-
tivation—a thin rectangle laid over the object lifeline.
5
Sequence diagrams sometimes are not the best way of representing complex parallel
activity. A UML model that is perhaps better suited for representing multithreaded
behavior is the activity diagram.
6
The way we’ve implemented it, the message send is synchronous, but since the oper-
ation is immediate and returns no information you can represent it as asynchronous.
COOPERATIVE AND PREEMPTIVE MULTITASKING 477
Meanwhile, the Server thread has sent the message start to its superclass
(or, more correctly, to itself). The run method overridden in Server begins exe-
cution. Its lifetime is indicated by an activation on the Server object’s lifeline.
Code in the run method sends the message remove(0) to the Queue to obtain and
remove its first Search element. Subsequently the run method sends the mes-
sage execute to the Search object and notifies the ResultsListener (via executed)
when execute completes.
Synchronization
One of the biggest pitfalls with multithreaded development is dealing with
the fact that threads run in nondeterministic order. Each time you execute a
multithreaded application, the threads may run in a different order that is
impossible to predict. The most challenging result of this nondeterminism is
that you may have coded a defect that surfaces only once in a few thousand
executions, or once every second full moon.8
Two threads that are executing code do not necessarily move through the
code at the same rate. One thread may execute five lines of code before an-
other has even processed one. The thread scheduler interleaves slices of code
from each executing thread.
Also, threading is not at the level of Java statements, but at a lower level
of the VM operations that the statements translate down to. Most statements
Synchronization
you code in Java are non-atomic: The Java compiler might create several in-
ternal operations to correspond to a single statement, even something as sim-
ple as incrementing a counter or assigning a value to a reference. Suppose an
assignment statement takes two internal operations. The second operation
may not complete before the thread scheduler suspends the thread and exe-
cutes a second thread.
All this code interleaving means that you may encounter synchronization
issues. If two threads go after the same piece of data at the same time, the re-
sults may not be what you expect.
You will modify the sis.studentinfo.Account class to support with-
drawing funds. To withdraw funds, the balance of the account must
be at least as much as the amount being withdrawn. If the amount is
too large, you should do nothing (for simplicity’s and demonstration’s
sake). In any case, you never want the account balance to go below zero.
package sis.studentinfo;
// ...
public class AccountTest extends TestCase {
// ...
private Account account;
8
The subtle gravitational pull might cause electrostatic tides that slow the processor
for a picosecond.
SYNCHRONIZATION 479
balance = balance.subtract(amount) 20
The second thread tests the balance after the first thread has approved the
balance but before the first thread has subtracted from the balance. Other ex-
ecution sequences could cause the same problem.
You can write a short test to demonstrate this problem.
480 MULTITHREADING
package sis.studentinfo;
import junit.framework.*;
import java.math.BigDecimal;
Creating t1.start();
Threads with t2.start();
Runnable t1.join();
t2.join();
There’s definitely some code redundancy in this method. After you under-
stand what the method is doing, make sure you refactor it to eliminate the
duplication.
When you execute JUnit, testConcurrency will in all likelihood pass. In a small
method, where everything executes quickly, the thread scheduler will proba-
bly allot enough time to a thread to execute the entire method before moving
on to the next thread. You can force the issue by inserting a pause in the
withdraw method:
Synchronized
Synchronized
Semantically, you want all of the code in withdraw to execute atomically. A
thread should be able to execute the entire withdraw method, start to finish,
without any other thread interfering. You can accomplish this in Java
through use of the synchronized method modifier.
public synchronized void withdraw(BigDecimal amount) {
if (amount.compareTo(balance) > 0)
return;
balance = balance.subtract(amount);
}
You should always try to lock the smallest amount of code possible, other-
wise you may experience performance problems while other threads wait to
obtain a lock. If you are creating small, composed methods as I’ve repeti-
tively recommended, you will find that locking at the method level suffices
for most needs. However, you can lock at a smaller atomicity than an entire
method by creating a synchronized block. You must also specify an object to use
as a monitor by enclosing its reference in parentheses after the synchronized key-
word.
The following implementation of withdraw is equivalent to the above imple-
mentation.
A synchronized block requires the use of braces, even if it contains only one
statement.
You can declare a class method as synchronized. When the VM executes a
class method, it will obtain a lock on the Class object for which the method
is defined.
Synchronized Collections
As I hinted earlier, the Server class contains a flaw with respect to the decla-
ration of the queue as a LinkedList.
When you work with the Java 2 Collection Class Framework with classes
such as ArrayList, LinkedList, and HashMap, you should be cognizant of the
fact that they are not thread-safe—the methods in these classes are not syn-
chronized. The older collection classes, Vector and Hashtable, are synchro-
nized by default. However, I recommend you do not use them if you need
thread-safe collections.
Instead, you can use utility methods in the class java.util.Collections to en-
close a collection instance in what is known as a synchronization wrapper.
The synchronization wrapper obtains locks where necessary on the target
BLOCKINGQUEUE 483
collection and delegates all messages off to the collection for normal process-
ing. Modify the Server class to wrap the queue reference in a synchronized list:
public class Server extends Thread {
private List<Search> queue =
Collections.synchronizedList(new LinkedList<Search>());
// ...
public void run() {
while (true) {
if (!queue.isEmpty())
execute(queue.remove(0));
Thread.yield();
}
}
BlockingQueue
A promising API addition in J2SE 5.0 is the concurrency library of classes,
located in the package java.util.concurrent. It provides utility classes to help
you solve many of the issues surrounding multithreading. While you should
prefer use of this library, its existence doesn’t mean that you don’t need to
learn the fundamentals and concepts behind threading.11
Since queues are such a useful construct in multithreaded applications, the
concurrency library defines an interface, BlockingQueue, along with five
11
The importance of learning how multiplication works before always using a calcu-
lator to solve every problem is an analogy.
484 MULTITHREADING
specialized queue classes that implement this interface. Blocking queues im-
plement another new interface, java.util.Queue, which defines queue seman-
tics for collections. Blocking queues add concurrency-related capabilities to
queues. Examples include the ability to wait for elements to exist when re-
trieving the next in line and the ability to wait for space to exist when storing
elements.
You can rework the Server class to use a LinkedBlockingQueue instead of
a LinkedList.
package sis.search;
import java.util.concurrent.*;
Stopping Threads
A testing flaw still remains in ServerTest. The thread in the Server class is ex-
ecuting an infinite loop. Normally, the loop would terminate when the Server
itself terminated. When running your tests in JUnit, however, the thread
keeps running until you close down JUnit itself—the Server run method keeps
chugging along.
The Thread API contains a stop method that would appear to do the trick
of stopping the thread. It doesn’t take long to read in the detailed API docu-
mentation that the stop method has been deprecated and is “inherently un-
safe.” Fortunately, the documentation goes on to explain why and what you
should do instead. (Read it.) The recommended technique is to simply have
the thread die on its own based on some condition. One way is to use a
boolean variable that you initialize to true and set to false when you want the
Stopping
thread to terminate. The conditional in the while loop in the run method can Threads
test the value of this variable.
You will need to modify the ServerTest method tearDown to do the shutdown
and verify that it worked.
Wait/Notify
Java provides a mechanism to help you coordinate actions between two or
more threads. Often you need to have one thread wait until another thread
has completed its work. Java provides a mechanism known as wait/notify.
You can call a wait method from within a thread in order to idle it. Another
thread can wake up this waiting thread by calling a notify method.
An example? Let’s rock! A clock—tic-toc!
In this example, you’ll build a clock class suitable for use in a
clock application. A user interface (UI) for the clock application
could display either a digital readout or an analog representation,
complete with hour, minute, and second hands. While the UI is busy
creating output that the user sees, the actual Clock class can execute a sepa-
WAIT/NOTIFY 487
rate thread to constantly track the time. The Clock thread will loop indefi-
nitely. Every second, it will wake up and notify the user interface that the
time has changed.
The UI class implements the ClockListener interface. You pass a ClockLis-
tener reference to a Clock object; the clock can call back to this listener with
the changed time. With this listener-based design, you could replace an ana-
log clock interface with a digital clock interface and not have to change the
clock class (see Figure 13.4).
The tough part is going to be writing a test. Here’s a strategy:
The test below performs each of these steps. The tricky bit is getting the
test to wait until the mock gets all the messages it wants. I’ll explain that
part, but I’ll let you figure out the rest of the test, including the verification of
the tics captured by the test listener.
package sis.clock;
import java.util.*;
import junit.framework.*;
AnalogClock GUIClock
After the test creates the Clock instance, it waits (line 5). You can send the
wait message to any object—wait is defined in the class Object. You must first
obtain a lock on that same object. You do so using either a synchronized block
(line 4) or synchronized method, as mentioned above. In the test, you use an
arbitrary object stored in the monitor field (line 1) as the object to lock and
wait on.
WAIT/NOTIFY 489
What does the test wait for? In the mock listener implementation, you can
store a count variable that gets bumped up by 1 each time the update(Date)
method is called. Once the desired number of messages is received, you can
tell the ClockTest to stop waiting.
From an implementation standpoint, the test thread waits until the lis-
tener, which is executing in another thread, says to proceed. The listener says
that things can proceed by sending the notifyAll message to the same object the
test is waiting on (line 3). In order to call notifyAll, you must first obtain a
lock against the monitor object, again using a synchronized block.
But . . . wait! The call to wait was in a synchronized block, meaning that no
other code can obtain a lock—including the synchronized block wrapping the
notifyAll method—until the synchronized block exits. Yet it won’t exit until the
waiting is done, and the waiting isn’t done until notifyAll is called. This would
seem to be a Catch-22 situation.
The trick: Behind the scenes, the wait method puts the current thread on
Wait/Notify
what is called a “wait set” for the monitor object. It then releases all locks
before idling the current thread. Thus when code in the other thread encoun-
ters the synchronized block wrapping the notifyAll call, there is no lock on the
monitor. It can then obtain a lock (line 2). The notifyAll call requires the lock
in order to be able to send its message to the monitor.
You may want to go over the discussion of wait/notify a few times until it
sinks in.
Here is the ClockListener interface and the Clock class implementation:
// sis.clock.ClockListener
package sis.clock;
import java.util.*;
// sis.clock.Clock
package sis.clock;
import java.util.*;
The run method in Clock uses the simple technique of testing a boolean
flag, run, each time through a while loop to determine when to stop. The stop
method, called by the test after its waiting is complete, sets the run boolean to
Wait/Notify false.
There is a flaw in the Clock implementation. The run method sleeps for at
least a full second. As I noted earlier, a sleep call may take a few additional
nanoseconds or milliseconds. Plus, creating a new Date object takes time.
This means that it’s fairly likely that the clock could skip a second: If the last
time posted was at 11:55:01.999, for example, and the sleep plus additional
execution took 1,001 milliseconds instead of precisely 1,000 milliseconds,
then the new time posted would be 11:55:03.000. The clock UI would show
11:55:01, then skip to 11:55:03. In an analog display, you’d see the second
hand jump a little bit more—not that big a deal in most applications. If you
run enough iterations in the test, it will almost certainly fail.
Here’s an improved implementation:
public void run() {
long lastTime = System.currentTimeMillis();
while (run) {
try { Thread.sleep(10); }
catch (InterruptedException e) {}
long now = System.currentTimeMillis();
if ((now / 1000) - (lastTime / 1000) >= 1) {
listener.update(new Date(now));
lastTime = now;
}
}
}
Could you have written a test that would have uncovered this defect con-
sistently? One option for doing so might include creating a utility class to re-
turn the current milliseconds. You could then mock the utility class to force a
certain time.
ADDITIONAL NOTES ON WAIT AND NOTIFY 491
The test for Clock is a nuisance. It adds 5 seconds to the execution time of
your unit tests. How might you minimize this time? First, it probably doesn’t
matter if the test proves 5 tics or 2. Second, you might modify the clock class
(and test accordingly) to support configurable intervals. Instead of 1 second,
write the test to demonstrate support for hundredths of a second (like a stop-
watch would require).
import java.util.*;
import java.util.concurrent.locks.*;
import junit.framework.*;
12
Which suggests you may not need to enclose an await call in a try-finally block.
Don’t risk it—you can’t guarantee that something won’t go awry with the code in
await.
494 MULTITHREADING
Coupled with the use of Condition objects, the idiom shown in ClockTest
effectively replaces the classic wait/notify scheme.
Thread Priorities
Threads can specify different priority levels. The scheduler uses a thread’s
priority as a suggestion for how often the thread should get attention. The
main thread that executes has a default priority assigned to it, Thread.NORM_
PRIORITY. A priority is an integer, bounded by Thread.MIN_PRIORITY on the low end
and Thread.MAX_PRIORITY on the high end.
You might use a lower priority for a thread that executes in the back-
Thread ground. In contrast, you might use a higher priority for user interface code
Priorities that needs to be extremely responsive. Generally, you will want to deviate
from NORM_PRIORITY only by small amounts, such as +1 and ⫺1. Deviating by
large amounts can create applications that perform poorly, with some
threads dominating and others starving for attention.
Every thread (other than the main thread) starts with the same priority as
the thread that spawned it. You can send a thread the message setPriority to
specify a different priority.
The following snippet of code from the Clock class shows how you might
set the priority for the clock to a slightly lower value.
Note use of the currentThread static method, which you can always call
to obtain the Thread object that represents the thread that is currently
executing.
THREADLOCAL 495
Deadlocks
If you are not careful in coding multithreaded applications, you can en-
counter deadlocks, which will bring your system to a screeching halt. Sup-
pose you have an object alpha running in one thread and an object beta
running in another thread. Alpha executes a synchronized method (which by
definition holds a lock on alpha) that calls a synchronized method defined on
beta. If at the same time beta is executing a synchronized method, and this
method in turn calls a synchronized method on alpha, each thread will wait
for the other thread to relinquish its lock before proceeding. Deadlock!13
Solutions for resolving deadlock:
1. Order the objects to be locked and ensure the locks are acquired in
this order.
2. Use a common object to lock. ThreadLocal
ThreadLocal
You may have a need to store separate information along with each thread
that executes. Java provides a class named ThreadLocal that manages creat-
ing and accessing a separate instance, per thread, of any type.
When you interact with a database through JDBC,14 you obtain a Connec-
tion object that manages communication between your application and the
database. It is not safe for more than one thread to work with a single con-
nection object, however. One classic use of ThreadLocal is to allow each
thread to contain a separate Connection object.
However, you’ve not learned about JDBC yet. For your example, I’ll de-
fine an alternate use for ThreadLocal.
In this example, you’ll take the existing Server class and add logging capa-
bilities to it. You want each thread to track when the search started and when
it completed. You also want the start and stop message pairs for a search to
appear together in the log. You could store a message for each event (start and
stop) in a common thread-safe list stored in Server. But if several threads were
to add messages to this common list, the messages would interleave. You’d
probably see all the start log events first, then all the stop log events.
13
[Arnold2000].
14
The Java DataBase Connectivity API. See Additional Lesson III for further informa-
tion.
496 MULTITHREADING
To solve this problem, you can have each thread store its own log mes-
sages in a ThreadLocal variable. When the thread completes, you can obtain
a lock and add all the messages at once to the complete log.
First, modify the Server class to create a new thread for each Search re-
quest. This should boost the performance in most environments, but you do
want to be careful how many threads you let execute simultaneously. (You
might experiment with the number of searches the test kicks off to see what
the limits are for your environment.) You may want to consider a thread pool
as mentioned in the section Additional Notes on wait and notify.
Ensure that your tests still run successfully. Next, let’s refactor
ServerTest—significantly. You want to add a new test that verifies the logged
messages. The current code is a bit of a mess, and it has a fairly long and in-
volved test. For the second test, you will want to reuse most of the nonasser-
tion code from testSearch. The refactored code also simplifies a bit of the
performance testing.
Here is the refactored test that includes a new test, testLogs.
package sis.search;
import junit.framework.*;
import java.util.*;
import sis.util.*;
The method testLogs executes the searches, waits for them to complete, and
verifies the logs. To verify the logs, the test requests the complete log from
the Server object, then loops through the log, extracting a pair of lines at a
time. It verifies that the search string (the first part of the log message) is the
same in each pair of lines.15
The modifications to the Server class:
package sis.search;
import java.util.concurrent.*;
import java.util.*;
15
See Lesson 8 for an in-depth discussion of Java logging and a discussion of whether
or not it is necessary to test logging code.
498 MULTITHREADING
ThreadLocal defines three additional methods: get, set, and remove. The get
and set methods allow you to access the current thread’s ThreadLocal in-
stance. The remove method allows you to remove the current thread’s Thread-
Local instance, perhaps to save on memory space.
In the search thread’s run method, you call the log method before and after
executing the search. In the log method, you access the current thread’s copy
of the log list by calling the get method on threadLog. You then add a pertinent
log string to the list.
Once the search thread completes, you want to add the thread’s log to the
complete log. Since you’ve instantiated completeLog as a synchronized collec-
tion, you can send it the addAll method to ensure that all lines in the thread log
are added as a whole. Without synchronization, another thread could add its
log lines, resulting in an interleaved complete log.
import junit.framework.*;
import sis.util.*;
scheduler.stop();
assertEquals(expectedResultsCount, actualResultsCount);
}
}
package sis.search;
import java.util.*;
16
[Sun2004b].
THREAD MISCELLANY 501
Thread Miscellany
Atomic Variables and volatile
Java compilers try to optimize as much code as possible. For example, if you
assign an initial value to a field outside of a loop:
private String prefix;
void show() throws Exception {
prefix = ":";
for (int i = 0; i < 100; i++) {
System.out.print(prefix + i);
Thread.sleep(1);
}
}
the Java compiler may figure that the value of the prefix field is fixed for the
duration of the show method. It may optimize the code to treat the use of prefix
within the loop as a constant. Java thus may not bother reading the value of
prefix with each iteration through the loop. This is an appropriate assump-
tion, as long as no other code (in a multithread execution) updates the value
of prefix at the same time. But if another thread does modify prefix, code in the
loop here may never see the changed value.17
17
The actual behavior may depend on your compiler, JVM, and processor configura-
tion.
502 MULTITHREADING
You can force Java to always read a fresh value for a field by enclosing ac-
cess to it in a synchronized block. Java uses this awareness that the code could
be executing in multiple threads to ensure that it always gets the latest value
for all fields contained within. Another way to get Java to read a fresh value
is to declare the field as volatile. Doing so essentially tells the compiler that it
cannot optimize access to the field.
When you use a shared boolean variable as the conditional for a thread’s run
loop, you may want to declare the boolean variable as volatile. This will ensure
that the while loop reads the updated value of the boolean each time through the
loop.
A related consideration is how Java treats common access to fields ac-
cessed from multiple threads. Java guarantees that reading or writing a vari-
able—with the exception of a long or double variable—is atomic. This means
that the smallest possible operation against the variable is to read or write its
entire value. It is not possible for one thread to write a portion of the variable
Thread
Miscellany while another thread is writing another portion, which would corrupt the
variable.
Thread Information
As mentioned earlier, you can obtain the Thread object for the currently exe-
cuting thread by using the Thread static method currentThread. The Thread ob-
ject contains a number of useful methods for obtaining information about
the thread. Refer to the Java API documentation for a list of these getter and
query methods. Additionally, using setName and getName, you can set and retrieve
an arbitrary name for the thread.
Shutting Down
The main thread in an executing application is by default a user thread. As
long as there is at least one active user thread, your application will continue
to execute. In contrast, you can explicitly designate a thread as a daemon
thread. If all user threads have terminated, the application will terminate as
well, even if daemon threads continue to execute.
The ThreadTest example demonstrates this behavior with respect to user
threads. The code executing in main runs in a user thread. Any threads
spawned inherit the execution mode (user, or daemon) of the thread that
spawned them, so thread t in the example is a user thread.
When you execute ThreadTest, it will not stop until you force termination
of the process (Ctrl-c usually works from the command line).
Setting a Thread object to execute as a daemon thread is as simple as send-
ing it the message setDaemon with a parameter value of true. You must set the
thread execution mode prior to starting the thread. Once a thread has
started, you cannot change its execution mode.
Thread
public class ThreadTest { Miscellany
public static void main(String[] args) throws Exception {
Thread t = new Thread() {
public void run() {
while (true)
System.out.print(‘.’);
}
};
t.setDaemon(true);
t.start();
Thread.sleep(100);
}
}
The exit method requires you to pass it an int value. This value represents
the application return code, which you can use to control shell scripts or
batch files.
In many applications, such as Swing applications, you will not have con-
trol over other threads that are spawned. Swing itself executes code in
threads. More often than not, threads end up being initiated as user threads.
Accordingly, you may need to use the exit method to terminate a Swing appli-
cation.
Managing Exceptions
When a run method throws an (unchecked18) exception, the thread in which it
is running terminates. Additionally, the exception object itself disappears.
You may wish to capture the exception, as ServerTest.testException suggests.
Thread public void testException() throws Exception {
Miscellany final String errorMessage = "problem";
Search faultySearch = new Search(URLS[0], "") {
public void execute() {
throw new RuntimeException(errorMessage);
}
};
server.add(faultySearch);
waitForResults(1);
List<String> log = server.getLog();
assertTrue(log.get(0).indexOf(errorMessage) != -1);
}
The test uses a mock override of the Search execute method to simulate a
RuntimeException being thrown. Your expectation is that this exception will
18
Since the Runnable run method signature declares no exceptions, you cannot over-
ride it to throw any checked exceptions.
THREAD MISCELLANY 505
be logged to the Server as a failed search. Here is the Server class implemen-
tation:
After creating the thread object, but before starting it, you can set an un-
caught exception handler on the thread. You create an uncaught exception
handler by implementing the method uncaughtException. If the thread throws an
exception that is not caught, the uncaughtException method is called. Java passes
uncaughtException both the thread and throwable objects.
Thread Groups
Thread groups provide a way to organize threads into arbitrary groups. A
thread group can also contain other thread groups, allowing for a contain-
ment hierarchy. You can control all threads contained in a thread group as a
unit. For example, you can send the message interrupt to a thread group,
which will in turn send the interrupt message to all threads contained within
the hierarchy.
In Effective Java, Joshua Bloch writes that thread groups “are best viewed
as an unsuccessful experiment, and you may simply ignore their existence.”19
They were originally designed to facilitate security management in Java but
never satisfactorily fulfilled this purpose.
Prior to J2SE 5.0, one useful purpose for thread groups was to monitor
threads for uncaught exceptions. However, you can now fulfill this need by
19
[Bloch 2001].
506 MULTITHREADING
Atomic Wrappers
Even simple arithmetic operations are not atomic. As of J2SE 5.0, Sun sup-
plies the package java.util.concurrent.atomic. This package includes a dozen
Atomic wrapper classes. Each class wraps a primitive type (int, long, or
boolean), a reference, or an array. By using an atomic wrapper, you guarantee
that operations on the wrapped value are thread safe and work against an
up-to-date value (as if you had declared the field volatile).
Here is an example use:
AtomicInteger i = new AtomicInteger(50);
assertEquals(55, i.addAndGet(5));
assertEquals(55, i.get());
Exercises
Refer to the Java API documentation for further details.
Exercises
1. Create an AlarmClock class. A client can submit a single event and
corresponding time. When the time passes, the AlarmClock sends an
alarm to the client.
EXERCISES 507
Exercises
This page intentionally left blank
Lesson 14
Generics
Parameterized Types
You generally develop a collection so that it can contain objects of any type.
However, in most cases, collections are useful only if you constrain them to
hold objects of a single (set of) types: a list of students, a words-to-definitions
map, and so on. You could consider this to be the Single-Responsibility
509
510 GENERICS
Collection Framework
Sun has parameterized all collection classes in the Collections Framework. A
simple language-based test demonstrates a simple use of the parameterized
type ArrayList:
final String name = "joe";
List<String> names = new ArrayList<String>(); // 1
names.add(name); // 2
String retrievedName = names.get(0); // 3
assertEquals(name, retrievedName);
Both the implementation class ArrayList and the interface List are bound
to the String class (line 1). You can add String objects to name (2). When retriev-
ing from name and assigning to a String reference (3), you need not cast to
String.
If you attempt to insert an object of any other type:
names.add(new Date()); // this won’t compile!
The Map interface and HashMap class are declared as Map<K,V> and
HashMap<K,V>, respectively. You must supply two bind types when using
these—one for the key (K) and one for the value (V).
1
You are not required to supply any bind types; however, this is not recommended.
See the section in this lesson entitled Raw Types.
512 GENERICS
package sis.util;
import junit.framework.*;
import java.util.*;
The method getSoleEvent is of some interest. After retrieving the events col-
lection stored at a date using the Map method get, you must ensure that it
contains only one element. To retrieve the sole element, you can create an it-
erator and return the first element it points to. You must bind the Iterator ob-
ject to the same type that you bound the collection to (String in this
example).
From an implementation standpoint, there is more than one way to build
a MultiHashMap. The easiest is to use a HashMap where each key is associ-
ated with a collection of values. For this example, you will define Multi-
HashMap to encapsulate and use a HashMap.
In order to support the existing tests, the implementation of Multi-
HashMap is simplistic. Each method size, put, and get will delegate to the en-
capsulated HashMap:
package sis.util;
import java.util.*;
The put method first extracts a list from map using the key passed in. If there
is no entry at the key, the method constructs a new ArrayList bound to the
value type V and puts this list into map. Regardless, value is added to the list.
Like HashMap, the type parameter list contains two type parameters, K
and V. Throughout the definition for MultiHashMap, you will see these sym-
bols where you might expect to see type names. For example, the get method
returns V instead of, say, Object. Each use of a type parameter symbol within
the type declaration is known as a naked type variable.
514 GENERICS
The Date type corresponds to the type parameter K, and the String type
corresponds to the type parameter V. Thus, the embedded map field would be a
HashMap<Date,List<String>>—a hash map whose key is bound to a date
and whose value is bound to a list of strings.
Erasure
Erasure
There is more than one way that Sun might have chosen to implement sup-
port for parameterized types. One possible way would have been to create a
brand-new type definition for each type to which you bind a parameterized
class. In binding to a type, each occurrence of a naked type variable in the
source would be replaced with the bind type. This technique is used by C++.
For example, were Java to use this scheme, binding MultiHashMap to
<Date,String> would result in the following code being created behind the
scenes:
// THIS ISN’T HOW JAVA TRANSLATES GENERICS:
package sis.util;
import java.util.*;
if (values == null) {
values = new ArrayList<String>();
map.put(key, values);
}
values.add(value);
}
package sis.util;
import java.util.*;
restrictions on the use of parameterized types that exist because of the era-
sure scheme. I’ll discuss these limitations throughout this chapter. Each possi-
ble scheme for implementing generics has its downsides. Sun chose the
erasure scheme for its ability to provide ultimate backward compatibility.
Upper Bounds
As mentioned, every type parameter has a default upper bound of Object.
You can constrain a type parameter to a different upper bound. For example,
you might want to supply an EventMap class, where the key must be bound
to a Date type—either java.util.Date or java.sql.Date (which is a subclass of
java.util.Date). A simple test:
package sis.util;
import junit.framework.*;
import java.util.*;
The EventMap class itself has no different behavior, only additional con-
straints on the type parameter K:
package sis.util;
You use the extends keyword to specify the upper bound for a type parame-
ter. In this example, the K type parameter for EventMap has an upper bound
of java.util.Date. Code using an EventMap must bind the key to a
java.util.Date or a subclass of java.util.Date (such as java.sql.Date). If you at-
tempt to do otherwise:
EventMap<String,String> map = new EventMap<String,String>();
UPPER BOUNDS 517
The compiler replaces naked type variables in generated code with the
upper bound type. This gives you the ability to send more specific messages
to naked type objects within the generic class.
You want the ability to extract all event descriptions from the EventMap
for events where the date has passed. Code the following test in Event-
MapTest.
public void testGetPastEvents() {
EventMap<Date,String> events = new EventMap<Date,String>();
final Date today = new java.util.Date();
final Date yesterday =
new Date(today.getTime() - 86400000);
events.put(today, "sleep");
final String descriptionA = "birthday"; Upper Bounds
final String descriptionB = "drink";
events.put(yesterday, descriptionA);
events.put(yesterday, descriptionB);
List<String> descriptions = events.getPastEvents();
assertTrue(descriptions.contains(descriptionA));
assertTrue(descriptions.contains(descriptionB));
}
Within EventMap, you can presume that objects of type K are Date
objects:
package sis.util;
import java.util.*;
when.setTime(date);
Calendar today = new GregorianCalendar();
if (when.get(Calendar.YEAR) != today.get(Calendar.YEAR))
return when.get(Calendar.YEAR) < today.get(Calendar.YEAR);
return when.get(Calendar.DAY_OF_YEAR) <
today.get(Calendar.DAY_OF_YEAR);
}
}
The entrySet method returns a set bound to the Map.Entry type. Each
Map.Entry object in the Set is in turn bound to the key type (K) and a list of
the value type (List<V>).
Wildcards
Wildcards
Sometimes you’ll write a method where you don’t care about the type to
which a parameter is bound. Suppose you need a utility method that creates
a single string by concatenating elements in a list, separating the printable
representation of each element with a new line. The StringUtilTest method
testConcatenateList demonstrates this need:
package sis.util;
import junit.framework.*;
import java.util.*;
assertEquals(String.format(“a%nb%n"), output);
}
}
WILDCARDS 519
In the StringUtil method concatenate, you will append each list element’s
string representation to a StringBuilder. You can get the string representation
of any object, provided by the toString method, without knowing or caring
about its type. Thus, you want to be able to pass a List that is bound to any
type as the argument to concatenate.
You might think that you can bind the list parameter to Object:
// this won’t work
public static String concatenate(List<Object> list) {
StringBuilder builder = new StringBuilder();
for (Object element: list)
builder.append(String.format("%s%n", element));
return builder.toString();
}
By binding list to Object, you constrain it to hold objects only of type Ob-
ject—and not of any Object subclasses. You cannot assign a List<String> ref-
erence to a List<Object> reference. If you could, client code could add an
Object to list using the List<Object> reference. Code that then attempted to
extract from list using the List<String> reference would unexpectedly retrieve
an Object.
Wildcards
Instead, Java allows you to use a wildcard character (?) to represent any
possible type:
package sis.util;
import java.util.*;
Within the concatenate method body, you cannot use the ? directly as a naked
type variable. But since list can contain any type of object, you can assign
each of its elements to an Object reference in the for-each loop.
Additionally, you can constrain a wildcard to an upper bound using the
extends clause.
For a second string utility method, you need to be able to concate-
nate a list of numerics, whether they are BigDecimal objects or Inte-
ger objects. Several tests drive out the minor differences between
decimal output and integral output:
520 GENERICS
import java.util.*;
import junit.framework.*;
IMPLICATIONS OF USING WILDCARDS 521
The pad method declares the list parameter to be a List that can be bound to
any type:
package sis.util;
import java.util.*;
The error you receive indicates that the compiler doesn’t recognize an appro-
priate add method.
cannot find symbol
symbol : method add(java.lang.Object)
location: interface java.util.List<?>
list.add(object);
^
The problem is that the wildcard ? designates an unknown type. Suppose that
the List is bound to a Date:
List<Date> list = new ArrayList<Date>();
The pad method doesn’t know anything about the specific type of the ob-
ject being passed in, nor does it know anything about the type to which the
list is bound. It can’t guarantee that a client isn’t trying to subvert the type
safety of the list. Java just won’t allow you to do this.
There is still a problem even if you specify an upper bound on the wild-
card.
522 GENERICS
Generic Methods
How do you solve the above problem? You can declare the pad method as a
Wildcard generic method. Just as you can specify type parameters for a class, you can
Capture
specify type parameters that exist for the scope of a method:
public static <T> void pad(List<T> list, T object, int count) {
for (int i = 0; i < count; i++)
list.add(object);
}
The compile can extract, or infer, the type for T based on the arguments
passed to pad. It uses the most specific type that it can infer from the argu-
ments. Your test should now pass.
Generic method type parameters can also have upper bounds.
You will need to use generic methods when you have a dependency be-
tween an argument to a method and either another argument or the return
type. Otherwise, you should prefer the use of wildcards. In the pad method de-
claration, the object parameter’s type is dependent upon the type of the list
parameter.
Wildcard Capture
The technique of introducing a generic method to solve the above problem is
known as wildcard capture. Another example is a simple method that swaps
the elements of a list from front to back.
SUPER 523
Super
Upper-bounded wildcards, which you specify using extends, are useful for
reading from a data structure. You can support writing to a data structure
using lower-bounded wildcards.
524 GENERICS
MultiHashMap<String,java.util.Date> mondayMeetings =
new MultiHashMap<String,java.util.Date>();
Super MultiHashMap.filter(mondayMeetings, meetings,
new MultiHashMap.Filter<java.util.Date>() {
public boolean apply(java.util.Date date) {
return isMonday(date);
}
});
assertEquals(2, mondayMeetings.size());
assertEquals(2, mondayMeetings.get("iteration start").size());
assertNull(mondayMeetings.get("brown bags"));
assertEquals(1, mondayMeetings.get("VP blather").size());
}
...
public class MultiHashMap <K,V> {
private Map<K, List<V>> map = new HashMap<K, List<V>>();
...
public interface Filter<T> {
boolean apply(T item);
}
Additional Bounds
It is possible to specify additional bounds, or more than one type, in an ex-
tends clause. While the first constraint can be either a class or interface, sub-
sequent constraints must be interface types. For example:
public static
<T extends Iterable&Comparable<T>> void iterateAndCompare(T t)
The passed-in collection could hold any type of object, but the max method
depended upon the collection holding objects that implemented the Com-
parable interface. An initial stab at a solution using generics could insist that
the collection hold Comparable objects:
public static
<T extends Comparable<? super T>> T max(Collection<? extends T> c)
Per J2SE specification, a type variable gets erased to its leftmost bound—
Object in this example, and not Comparable. The max method now returns an
Object reference but also defines the additional constraint that the collection
objects must implement the appropriate Comparable interface.
Raw Types
If you work with an existing system, written in J2SE 1.4 or earlier, it will use
collection classes that do not support parameterization. You’ll have plenty of
code such as:
List list = new ArrayList();
list.add("a");
RAW TYPES 527
You can still use this code under J2SE 5.0. When you use a generic type
without binding it to a type parameter, you refer to the type as a raw type.
The use of raw types is inherently not typesafe: You can add objects of any
type to a raw collection and encounter runtime exceptions when retrieving an
object of unexpected type. This is the fundamental reason why Sun intro-
duced parameterized types.
Any such unsafe operation is met with a warning by the compiler. Using
the default compiler options, the above two lines of code that add to a raw
ArrayList result in the following compiler message:
Under Ant, supply an additional nested element on the javac task: Raw Types
Recompiling will present you with more specific details on the source of
each unchecked warning:
warning: [unchecked] unchecked call to add(E) as a member of the raw type java.util.List
list.add("a");
^
Checked Collections
Suppose you’re working with legacy code. In fact, most systems you en-
counter will include code written for older versions of Java. This is the reality
of Java development. I still encounter systems that make pervasive use of
Vector and Hashtable—even though Sun recommended use of collections
framework classes (List/ArrayList, etc.) instead as of Java 1.2. That was over
six years ago!
You will likely encounter raw collections for some time coming. You can
quickly add some time safety to your code by using a checked wrapper. Sup-
pose your existing mess of a system creates a list intended to hold Integer
wrapper objects:
List ages = new ArrayList();
2
And of course none of those dozens of classes will have tests. The vast majority of
legacy systems have no tests.
CHECKED COLLECTIONS 529
the exception, not the code that attempts to extract it. This will make for
quicker debugging efforts. You can make the change in one place:
When the Java VM executes this line of code, you receive an exception:
is the ArrayList object you are creating; it is bound to the Integer type. The
second argument is a Class reference to the Integer type. Each time you in-
voke the add method, it uses the class reference to determine whether or not
the passed parameter is of that type. If not, it throws a ClassCastException.
Checked wrappers require the redundancy of having to pass in a type (In-
teger.class) reference in addition to specifying a bind type (<Integer>). This is
required because of erasure: the bind type information is not available to the
list object at runtime. You can encapsulate unchecked wrappers in your own
parameterized types, but you will then need to require clients to pass in the
Class reference.
The Collection class provides checked collection wrappers for the types
Collection, List, Map, Set, SortedMap, and SortedSet.
Even if you are writing J2SE 5.0 code, using checked collections can pro-
tect you from loose and fast developers beyond your sphere of influence.
Some cowboy and cowgirl developers enjoy subverting the rules whenever
they can. In the case of parameterized types, Java lets them.
You can cast an object of a parameterized type to a raw type. This allows
you to store an object of any type in the collection. You will receive a compi-
lation warning, but you can ignore the warning. Do so at your own peril.
The results will generally be disastrous. This sort of strategy falls under the
category of “don’t do that!”
530 GENERICS
Using checked collections in 5.0 code can help solve this problem. The ex-
ceptions thrown will come from the sneaky code, letting you pinpoint the
source of trouble.
Arrays
You cannot create arrays of bounded parameterized types:
Again, the problem is that you could easily assign the parameterized type
reference to another type reference. You could then add an inappropriate ob-
Arrays
ject, causing a ClassCastException upon attempted extraction:
Java does allow you to create arrays of parameterized types that are un-
bounded (i.e., where you use only the wildcard character ?):
The chief distinction is that you must acknowledge the potential for prob-
lems by casting, since the List is a collection of objects of unknown type.
ADDITIONAL LIMITATIONS 531
Additional Limitations
Due to the erasure scheme, an object of a parameterized type has no informa-
tion on the bind type. This creates a lot of implications about what you can
and cannot do.
You cannot create new objects using a naked type variable:
package util;
import java.util.*;
unexpected type
found : type parameter T
required: class
T zero = new T(0);
^
Erasure means that a naked type variable erases to its upper bound, Num-
ber in this example. In most cases, the upper bound is an abstract class such
as Number or Object (the default), so creating objects of that type would not
be useful. Java simply prohibits it.
You can cast to a naked type variable, but for the same reason, it is not
often useful to do so. You cannot use a naked type variable as the target of
the instanceof operator.
You can use type variables in a generic static method. But you cannot use
the type variables defined by an enclosing class in static variables, static
methods, or static nested classes. Because of erasure, there is only one class
definition that is shared by all instances, regardless of the type to which they
are bound. The class definition is created at compile time. This means that
sharing static elements wouldn’t work. Each client that binds the parameter-
ized type to something different would expect to have the static member con-
strained to the type they specified.
532 GENERICS
Reflection
The reflection package has been retrofitted to support providing parameter
information for parameterized types and methods. For example, you can
send the message getTypeParameters to a class in order to retrieve an array of
TypeParameter objects; each TypeParameter object provides you with enough
information to be able to reconstruct the type parameters.
To support these changes, Sun altered the Java byte code specification.
Class files now store additional information about type parameters. Most
significant, the Class class has been modified to be a parameterized type,
Class<T>. The following assignment works:
Class<String> klass = String.class;
If you’re interested in how you might use this, take a look at the source for
the CheckedCollection class. It’s a static inner class of java.util.Collections.
The reflection modifications provide you with information on the declara-
tion of parameterized types and methods. What you will not get from reflec-
Final Notes tion is information about the binding of a type variable. If you bind an
ArrayList to a String, that information is not known to the ArrayList object
because of the erasure scheme. Thus reflection has no way of providing it to
you. It would be nice to be able to code
public class MultiHashMap<K,V> {
...
public Class<V> getKeyType() {
return V.class; // this will not work!
}
}
Final Notes
As you’ve seen, knowing how the erasure scheme works is key to being able
to understand and implement parameterized types. From a client perspective,
using parameterized types is relatively easy and imparts the significant benefit
of type safety. From the perspective of a developer creating parameterized
types, it can be a fairly complex adventure.
If you find yourself struggling with how to understand or define a parame-
terized type, distill the definition and sample use of the parameterized type
EXERCISES 533
into its erased equivalent. Also, take a look at some of the uses in the J2SE
5.0 source code. The collections framework classes and interfaces, such as
Map and HashMap, provide some good examples. For more complex exam-
ples, take a look at java.util.Collections.
Exercises
1. Create a new parameterized collection type—a Ring. A Ring is a cir-
cular list that maintains knowledge of a current element. A client can
retrieve and remove the current element. The Ring must support ad-
vancing or backing up the current pointer by one position. The
method add adds an element after the current element. The Ring class
should support client iteration through all elements, starting at the
current pointer, using for-each. The Ring class should throw appropri-
ate exceptions on any operation that is invalid because the ring is
empty.
Do not use another data structure to store the ring (e.g., a
Exercises
java.util.LinkedList). Create your own link structure using a nested
node class. Each node, or entry, should contain three things: the data
element added, a reference to the next node in the circle, and a refer-
ence to the previous node in the circle.
This page intentionally left blank
Lesson 15
Assertions
You have been using assert methods defined as part of the JUnit API. These
assert methods, defined in junit.framework.Assert, throw an AssertionFailed-
Error when appropriate.
535
536 ASSERTIONS AND ANNOTATIONS
Java supports a similar assertion feature that you can turn on or off using
a VM flag. An assertion statement begins with the keyword assert. It is fol-
lowed by a conditional; if the conditional fails, Java throws a RuntimeExcep-
tion of type AssertionError. You can optionally supply a message to store in
the AssertionError by following the conditional with a colon and the String
message. An example:
Assertions are disabled by default. When assertions are disabled, the VM ig-
nores assert statements. This prevents assert statements from adversely affect-
ing application performance. To turn assertions on:
Both of these statements will enable assertions for all your code but not
for the Java library classes. You can turn assertions off using -da or -disable-
assertions. To turn assertions on or off for system classes, use -enablesystemasser-
tions (or -esa) or -disablesystemassertions (or -dsa).
Java allows you to enable or disable assertions at a more granular level.
You can turn assertions on or off for any individual class, any package and
all its subpackages, or the default package.
For example, you might want to enable assertions for all but one class in a
package:
The example shown will enable assertions for all classes in sis.studentinfo
with the exception of Session. The ellipses means that Java will additionally
enable assertions for any subpackages of sis.studentinfo, such as sis.studentinfo
.ui. You represent the default package with just the ellipses.
Assertions are disabled/enabled in the order in which they appear on the
java command line, from left to right.
ANNOTATIONS 537
Annotations
You have already seen some examples of built-in annotations that Java sup-
ports. In Lesson 2 you learned how you can mark a method as @deprecated,
meaning that the method is slated for eventual removal from the public inter-
face of a class. The @deprecated annotation is technically not part of the Java
538 ASSERTIONS AND ANNOTATIONS
language. Instead, it is a tag that the compiler can interpret and use as a basis
for printing a warning message if appropriate.1
You have also seen examples of javadoc tags, annotations that you embed
in javadoc comments and that you can use to generate Java API documenta-
tion web pages. The javadoc.exe program reads through your Java source
files, parsing and interpreting the javadoc tags for inclusion in the web out-
put.
Also, in Lesson 9, you learned how you could use the @Override tag to indi-
cate that you believed you were overriding a superclass method.
You can also build your own annotation tags for whatever purpose you
might find useful. For example, you might want the ability to mark methods
with change comments:
@modified("JJL", "12-Feb-2005")
public void cancelReservation() {
// ...
}
1
@deprecated existed in the Java language from its early days and long before Sun intro-
duced formalized annotations support. But it works just like any other compiler-level
annotation type.
TESTRUNNERTEST 539
testing tool using TDD, because it’s not intended to be production code. But
there’s nothing that says you can’t write tests. We will.
JUnit requires a test class to extend from the class junit.framework.Test-
Case. In TestRunner you will use a different mechanism than inheritance:
You will use Java annotations to mark a class as a test class.
Getting started is the toughest part, but using Ant can help. You can set up
an Ant target to represent the user interface for the test. If not all of the tests
pass, you can set things up so that the Ant build fails, in which case you will
see a “BUILD FAILED” message. Otherwise you will see a “BUILD SUC-
CESSFUL” message.
TestRunnerTest
The first test in TestRunnerTest, singleMethodTest, goes up against a secondary class
SingleMethodTest defined in the same source file. SingleMethodTest provides a
single empty test method that should result in a pass, as it would in JUnit.
So far you need no annotations. You pass a reference to the test class,
TestRunnerTest.class, to an instance of TestRunner. TestRunner can assume
that this parameter is a test class containing only test methods.
To generate test failures, you will use the assert facility in Java, described in
the first part of this lesson. Remember that you must enable assertions when TestRunnerTest
executing the Java VM; otherwise, Java will ignore them.
Here is TestRunnerTest.
package sis.testing;
import java.util.*;
import java.lang.reflect.*;
Iterator<Method> it = testMethods.iterator();
Method method = it.next();
assert methodNames.contains(testMethodNameA):
"expected " + testMethodNameA + " as test method";
assert methodNames.contains(testMethodNameB):
"expected " + testMethodNameB + " as test method";
}
}
class SingleMethodTest {
public void testA() {}
}
TestRunner
First, let’s go over the initial implementation of TestRunner.
package sis.testing;
import java.util.*;
import java.lang.reflect.*;
TESTRUNNER 541
class TestRunner {
private Class testClass;
private int failed = 0;
private Set<Method> testMethods = null;
2
You can use the slightly-more-succinct idiom new Object[0] in place of new Object[] {}.
542 ASSERTIONS AND ANNOTATIONS
}
catch (Throwable t) {
t.printStackTrace();
failed++;
}
}
import java.util.*;
import java.lang.reflect.*;
@TestMethod
public void singleMethodTest() {
runTests(SingleMethodTest.class);
verifyTests(methodNameA);
}
@TestMethod
public void multipleMethodTest() {
runTests(MultipleMethodTest.class);
verifyTests(methodNameA, methodNameB);
}
544 ASSERTIONS AND ANNOTATIONS
class SingleMethodTest {
@TestMethod public void testA() {}
}
class MultipleMethodTest {
@TestMethod public void testA() {}
@TestMethod public void testB() {}
}
The @TestMethod annotation may appear after any method modifiers such as
publicor static. The annotation must appear before the signature of the
method (which starts with the return type of the method).
To declare the @TestMethod annotation type, you create what looks a lot like
an interface declaration:
RETENTION 545
package sis.testing;
public @interface TestMethod {}
One line of code is all it takes. You send the message isAnnotationPresent to
the Method object, passing in the type (TestMethod.class) of the annota-
tion. If isAnnotationPresent returns true, you add the Method object to the list of
tests. Retention
Now the test executes but returns improper results:
runAllTests:
[java] passed: 0 failed: 0
You’re expecting to see two passed tests but no tests are registered—the
method is always returning false.
isAnnotationPresent
Retention
The java.lang.annotations package includes a meta-annotation type named
@Retention. You use meta-annotations to annotate other annotation type decla-
rations. Specifically, you use the @Retention annotation to tell the Java compiler
how long to retain annotation information. There are three choices, summa-
rized in Table 15.1.
As explained by the table, if you don’t specify an @Retention annotation, the
default behavior means that you probably won’t be able to extract informa-
546 ASSERTIONS AND ANNOTATIONS
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
public @interface TestMethod {}
Annotation Targets
You have designed the @TestMethod annotation to be used by developers to mark
test methods. Annotations can modify many other element types: types
(classes, interfaces, and enums), fields, parameters, constructors, local vari-
3
The VM may choose to retain this information.
ANNOTATION TARGETS 547
ables, and packages. By default, you may use an annotation to modify any el-
ement. You can also choose to constrain an annotation type to modify one
and only one element type. To do so, you supply an @Target meta-annotation
on your annotation type declaration.
Since you didn’t specify an @Target meta-annotation for @TestMethod, a devel-
oper could use the tag to modify any element, such as a field. Generally no
harm would be done, but a developer could mark a field by accident, think-
ing that he or she marked a method. The test method would be ignored until
someone noticed the mistake. Adding an @Target to an annotation type is one
more step toward helping a developer at compile time instead of making him
or her decipher later troubles.
Add the appropriate @Target meta-annotation to @TestMethod:
package sis.testing;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TestMethod {}
// ...
public class TestRunnerTest {
private @TestMethod TestRunner runner;
@TestMethod
public void singleMethodTest() {
// ...
When you compile, you should see an error similar to the following:
Modifying TestRunner
Add a new test method, ignoreMethodTest, to TestRunnerTest. It will go up
against a new test class, IgnoreMethodTest, which contains three methods
marked with @TestMethod. One of the test methods (testC) is additionally marked
@Ignore. You must verify that this test method is not executed.
MODIFYING TESTRUNNER 549
package sis.testing;
import java.util.*;
import java.lang.reflect.*;
// ...
class IgnoreMethodTest {
@TestMethod public void testA() {}
@TestMethod public void testB() {}
@Ignore
@TestMethod public void testC() {}
}
The @Ignore annotation declaration looks a lot like the @TestMethod declara-
tion.
package sis.testing; Modifying
TestRunner
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Ignore {}
import java.util.*;
import java.lang.reflect.*;
class TestRunner {
...
private void loadTestMethods() {
testMethods = new HashSet<Method>();
for (Method method: testClass.getDeclaredMethods())
if (method.isAnnotationPresent(TestMethod.class) &&
!method.isAnnotationPresent(Ignore.class))
testMethods.add(method);
}
...
}
550 ASSERTIONS AND ANNOTATIONS
Single-Value Annotations
The @Ignore annotation is a marker annotation—it marks whether a
method should be ignored or not. You merely need to test for the
presence of the annotation using isAnnotationPresent. Now you need de-
velopers to supply a reason for ignoring a test. You will modify the @Ignore an-
notation to take a reason String as a parameter.
To support a single parameter in an annotation type, you supply a mem-
ber method named value with an appropriate return type and no parameters.
Annotation type member methods cannot take any parameters.
package sis.testing;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Ignore {
String value();
}
Rerun your tests. They pass, so you haven’t broken anything. But you also
want the ability to print a list of the ignored methods. Modify the test ac-
cordingly:
@TestMethod
public void ignoreMethodTest() {
runTests(IgnoreMethodTest.class);
verifyTests(methodNameA, methodNameB);
assertIgnoreReasons();
}
package sis.testing;
import java.util.*;
import java.lang.reflect.*;
class TestRunner {
// ...
private Map<Method, Ignore> ignoredMethods = null;
// ...
552 ASSERTIONS AND ANNOTATIONS
You can send the getAnnotation method to any element that can be anno-
tated, passing it the annotation type name (Ignore.class here). The getAnnotation
method returns an annotation type reference to the actual annotation object.
Once you have the annotation object reference (ignore), you can send mes-
sages to it that are defined in the annotation type interface.
You can now modify the text user interface to display the ignored methods.
A TestRunner
User Interface
Class
A TestRunner User Interface Class
At this point, the main method is no longer a couple of simple hacked-out
lines. It’s time to move this to a separate class responsible for presenting the
user interface.
Since TestRunner is a utility for test purposes, as I mentioned earlier, tests
aren’t absolutely required. For the small bit of nonproduction user interface
code you’ll write for the test runner, you shouldn’t feel compelled to test first.
You’re more than welcome to do so, but I’m not going to here.
The following listing shows a refactored user interface class that prints out
ignored methods. About the only thing interesting in it is the “clever” way I re-
turn the number of failed tests in the System.exit call. Why? Why not? It’s more
succinct than an if statement, it doesn’t obfuscate the code, and it returns addi-
tional information that the build script or operating system could use.
package sis.testing;
import java.lang.reflect.*;
import java.util.*;
ARRAY PARAMETERS 553
System.out.println("\nIgnored Methods");
for (Map.Entry<Method, Ignore> entry:
runner.getIgnoredMethods().entrySet()) {
Ignore ignore = entry.getValue();
System.out.println(entry.getKey() + ": " + ignore.value());
}
}
}
Array Parameters
You want to allow developers to provide multiple separate reason
strings. To do so, you can specify String[] as the return type for the
annotation type member value. An @Ignore annotation can then contain
multiple reasons by using a construct that looks similar to an array initializer:
554 ASSERTIONS AND ANNOTATIONS
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Ignore {
String[] value();
}
If you only need to supply a single string for an annotation type member
with a return type of String[], Java allows you to eliminate the array-style ini-
tialization. These annotations for the current definition of @Ignore are equivalent:
@Ignore("why")
@Ignore({"why"})
import java.util.*;
import java.lang.reflect.*;
@TestMethod
public void ignoreMethodTest() {
runTests(IgnoreMethodTest.class);
verifyTests(methodNameA, methodNameB);
assertIgnoreReasons();
}
class SingleMethodTest {
@TestMethod public void testA() {}
}
class MultipleMethodTest {
@TestMethod public void testA() {}
@TestMethod public void testB() {}
}
class IgnoreMethodTest {
@TestMethod public void testA() {}
@TestMethod public void testB() {}
@Ignore({TestRunnerTest.IGNORE_REASON1,
TestRunnerTest.IGNORE_REASON2})
@TestMethod public void testC() {}
}
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Ignore {
String[] reasons();
String initials();
}
556 ASSERTIONS AND ANNOTATIONS
class IgnoreMethodTest {
Multiple @TestMethod public void testA() {}
Parameter @TestMethod public void testB() {}
Annotations
@Ignore(
reasons={TestRunnerTest.IGNORE_REASON1,
TestRunnerTest.IGNORE_REASON2},
initials=TestRunnerTest.IGNORE_INITIALS)
@TestMethod public void testC() {}
}
System.out.println("\nIgnored Methods");
for (Map.Entry<Method, Ignore> entry:
runner.getIgnoredMethods().entrySet()) {
Ignore ignore = entry.getValue();
System.out.printf("%s: %s (by %s)",
entry.getKey(),
DEFAULT VALUES 557
Arrays.toString(ignore.reasons()),
ignore.initials());
}
}
Default Values
The reason for ignoring a test method is likely to be the same most
of the time. Most often, you will want to temporarily comment out a
test while fixing other broken tests. Supplying a reason each time can
be onerous, so you would like to have a default ignore reason. Here’s how
you might reflect this need in a TestRunner test:
@TestMethod
public void ignoreWithDefaultReason() {
runTests(DefaultIgnoreMethodTest.class);
verifyTests(methodNameA, methodNameB);
Map<Method, Ignore> ignoredMethods = runner.getIgnoredMethods();
Map.Entry<Method, Ignore> entry = getSoleEntry(ignoredMethods);
Ignore ignore = entry.getValue();
assert TestRunner.DEFAULT_IGNORE_REASON.
equals(ignore.reasons()[0]);
}
class DefaultIgnoreMethodTest {
@TestMethod public void testA() {} Default Values
@TestMethod public void testB() {}
@Ignore(initials=TestRunnerTest.IGNORE_INITIALS)
@TestMethod public void testC() {}
}
You can supply a default value on any annotation type member. The de-
fault must be a constant at compile time. The new definition of @Ignore in-
cludes a default value on the reasons member. Note use of the keyword default
to separate the member signature from the default value.
package sis.testing;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
558 ASSERTIONS AND ANNOTATIONS
@TestMethod
public void dateTest() {
runTests(IgnoreDateTest.class);
Map<Method, Ignore> ignoredMethods = runner.getIgnoredMethods();
Map.Entry<Method, Ignore> entry = getSoleEntry(ignoredMethods);
Ignore ignore = entry.getValue();
sis.testing.Date date = ignore.date();
Additional assert 1 == date.month();
Return Types assert 2 == date.day();
and Complex assert 2005 == date.year();
Annotation Types }
class IgnoreDateTest {
@Ignore(
initials=TestRunnerTest.IGNORE_INITIALS,
date=@Date(month=1, day=2, year=2005))
@TestMethod public void testC() {}
}
package sis.testing;
The @Ignore annotation type can now define a date member that returns a
testing.Date instance:
package sis.testing;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Ignore {
String[] reasons() default TestRunner.DEFAULT_IGNORE_REASON;
String initials();
Date date();
}
Since the Date annotation type is only used as part of another annotation,
it does not need to specify a retention or a target.
You may not declare a recursive annotation type—that is, an annotation
type member with the same return type as the annotation itself.
To get your tests to compile and pass, you’ll also need to modify
IgnoreMethodTest and DefaultIgnoreMethodTest:
class IgnoreMethodTest {
@TestMethod public void testA() {}
@TestMethod public void testB() {}
@Ignore(
reasons={TestRunnerTest.IGNORE_REASON1, Package
TestRunnerTest.IGNORE_REASON2}, Annotations
initials=TestRunnerTest.IGNORE_INITIALS,
date=@Date(month=1, day=2, year=2005))
@TestMethod public void testC() {}
}
class DefaultIgnoreMethodTest {
@TestMethod public void testA() {}
@TestMethod public void testB() {}
@Ignore(initials=TestRunnerTest.IGNORE_INITIALS,
date=@Date(month=1, day=2, year=2005))
@TestMethod public void testC() {}
}
Package Annotations
Suppose you want the ability to designate packages as testing
packages. Further, you want your testing tool to run “performance-
related” tests separately from other tests. In order to accomplish this,
560 ASSERTIONS AND ANNOTATIONS
you can create an annotation whose target is a package. A test for this anno-
tation:4
@TestMethod
public void packageAnnotations() {
Package pkg = this.getClass().getPackage();
TestPackage testPackage = pkg.getAnnotation(TestPackage.class);
assert testPackage.isPerformance();
}
You extract annotation information from the Package object like you
would from any other element object. You can obtain a Package object by
sending the message getPackage to any Class object.
The annotation declaration is straightforward:
package sis.testing;
import java.lang.annotation.*;
@Target(ElementType.PACKAGE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestPackage {
boolean isPerformance() default false;
}
However, the question is, Where does a package annotation go? Would it
go before the package statement in every source file that belongs to the pack-
Package age? Or before just one of them? Or is it stored elsewhere?
Annotations
Java limits you to at most one annotated package statement per package.
This means that you can’t just place an annotation before the package state-
ment in an arbitrary source file.
The answer depends on which compiler you are using. Sun recommends a
specific scheme that is based on a file system. Other compiler vendors may
choose a different scheme. They may have to if the compile environment is
not based on a file system.
The Sun scheme requires you to create a source file named package-
info.java in the source directory that corresponds to the package you want to
annotate. Sun’s Java compiler reads this pseudo–source file but produces no
visible class files as output. (In fact, it is not possible to include a hyphen [-]
in a class name.) This file should include any package annotations, followed
by the appropriate package statement. You should not include anything else in
package-info.java.
Here’s what package-info.java might look like in the sis.testing package:
@TestPackage(isPerformance=true) package sis.testing;
4
Note use of the variable name pkg, since package is a keyword.
ADDITIONAL NOTES ON ANNOTATIONS 561
Compatibility Considerations
Sun has, as much as possible, designed the annotations facility to support
changes to an annotation with minimal impact on existing code. This section
goes through the various modification scenarios and explains the impact of
each kind of change to an annotation type.
When you add a new member to an annotation type, provide a default if
possible. Using a default will allow code to continue to use the compiled an-
notation type without issue. But this can cause a problem if you try to access
the new member. Suppose you access the new member from an annotation
compiled using the annotation type declaration without the new member. If
no default exists on the new member, this will generate an exception.
If you remove an annotation type member, you will obviously cause errors
on recompilation of any likewise annotated sources. However, any existing
class files that use the modified annotation type will work fine until their
sources are recompiled.
Avoid removing defaults, changing the return type, or removing target ele-
ments with existing annotation types. These actions all have the potential to
generate exceptions.
If you change the retention type, the behavior is generally what you would
expect. For example, if you change from RUNTIME to CLASS, the annotation is no
Additional
longer readable at runtime. Notes on
When in doubt, write a test to demonstrate the behavior! Annotations
• You can modify an element only once with a given annotation. For ex-
ample, you cannot supply two @Ignore annotations for a test method.
• In order to internally support annotation types, Sun modified the Ar-
rays class to include implementations of toString and hashCode for Arrays.
Summary
Annotations are a powerful facility that can help structure the notes you put
into your code. One of the examples that is touted the most is the ability to
annotate interface declarations so that tools can generate code from the perti-
nent methods.
The chief downside of using annotations is that you make your code de-
pendent upon an annotation type when your code uses it. Changes to the an-
notation type declaration could negatively impact your code, although Sun
has built in some facilities to help maintain binary compatibility. You also
must have the annotation type class file present in order to compile. This
should not be surprising: An annotation type is effectively an interface type.
Again, the rule of thumb is to use annotation types prudently. An annota-
tion type is an interface and should represent a stable abstraction. As with
Exercises any interface, ensure that you have carefully considered the implications of
introducing an annotation type into your system. A worst-case scenario,
where you needed to make dramatic changes to an annotation type, would
involve a massive search and replace5 followed by a compile.
Exercises
1. Using the RingTest and Ring classes from the previous lesson, intro-
duce an assert into the add method that rejects a null argument. Make
sure you write a test! Don’t forget to enable assertions before running
your tests.
5
For the time being, IDEs might not provide sophisticated support for manipulating
and navigating annotations. You may need to resort to search and replace (or com-
pile, identify, and replace) to effect a major change. Integrated IDE support for anno-
tations should emerge quickly. The support in IDEA is already at a level I would
consider acceptable.
EXERCISES 563
Swing, Part 1
Swing is Java’s standard library for cross-platform GUI (graphical user inter-
face) development. The Swing API provides a rich feature set, allowing you
to construct sophisticated user interfaces. Using Swing, you can construct ap-
plications that allow the user to interact through basic controls1 such as push
buttons, entry fields, and list boxes or using advanced controls such as tables,
trees, and drag & drop.
This chapter introduces Swing. It is an overview that shows you the ba-
sics: how to construct simple applications using some of the more common
widgets. By no means does this chapter encompass everything about Swing.
In fact, it only scratches the surface. Authors have devoted entire books, and
even multiple volumes, to the topic.
Learning the basics of Swing, however, will provide you with a foundation
for understanding how the rest of it works and how to go about finding
more information. Once you have learned how to build a table widget using
a table model, for example, learning how to build a tree widget using a tree
model is easy. The Java API documentation usually provides sufficient infor- Swing, Part 1
mation about how to use a widget. For more complex widgets, including the
tree model and things such as sophisticated widget layouts, the Java API doc-
umentation often has a link to a good Sun tutorial on the topic.
More important, in this chapter you will learn various approaches to and
philosophies about how to test Swing applications. Developers often view
testing Swing applications as a difficult proposition and decide instead to
forgo it. As a result, Swing code is frequently an untested, poorly designed
mess.
This chapter is as much about ideas for testing Swing as it is about Swing
itself. Testing Swing applications can be difficult, but that isn’t an excuse to
1
Also known as widgets or components, controls are elements of graphical user inter-
faces that either present information or allow end users some aspect of control over
their interaction with the user interface.
565
566 SWING, PART 1
not do so. There are huge benefits to having well-tested, well-designed user
interface code.
There are two different aspects of design for user interfaces. The Java you
design and code to construct the user interface is the aspect you will concern
yourself with in this chapter. Another aspect of design is the aesthetic and
functional quality of the user interface—its look and feel.
The look and feel represents requirements—how does an end user need to
interact with the application? The customer, or perhaps a user interface de-
sign expert working with the customer, should derive and present these re-
quirements to the development team. Many shops involve members of the
development team in the GUI design as well. The format in which the cus-
tomer presents requirements to the developers could be screen snapshots,
crude drawings, or various formalized diagrams.
You will learn about:
• Swing application design
• panels and frames
• buttons and action listeners
• lists and list models
• Swing layouts: BorderLayout, GridLayout, FlowLayout, BoxLayout,
GridBagLayout
Swing Swing
Swing is not your only choice for GUI development, but it is available to you
“out of the box.” The Eclipse IDE was built using SWT (Standard Widget
Toolkit), an API that you could also choose to use for your applications.
Swing is actually built atop another Java API known as AWT (Abstract
Window Toolkit). Sun introduced AWT with the first version of Java as the
sole means for producing GUIs. It consisted of a small number of widgets
that at the time were guaranteed to be available on all platforms supported
by Java. This “lowest common denominator” design decision allowed you to
build cross-platform GUI applications but restricted your expressiveness with
respect to GUI design.
AWT is known as a heavyweight GUI framework. Java tightly couples
each AWT control to an equivalent control directly supported by the operat-
ing system. The operating system manages each AWT control.
Swing, which was introduced a few years after AWT, is, in contrast, a
lightweight GUI framework. Swing eliminates the lowest common denomina-
GETTING STARTED 567
Getting Started
You will build a simple Swing application that presents a lists of
courses to the end user. It will also allow the user to add new
courses.
As you learn Swing, you might start with spike solutions—bits of code
that demonstrate how Swing should work. Once you get these spikes work-
ing, you will go back and figure out how to test the Swing code. This chapter
sometimes presents its material in this fashion: I will demonstrate how to
code something in Swing, then supply code that shows how to test it.
Most applications, whether they use Swing or not, initiate with a frame. A
frame is a top-level window with a title bar and border by default. The frame
is largely drawn by and under the control of your operating system. Within
Java, the Swing frame class is javax.swing.JFrame. Building an initial appli-
cation in Java is as simple as constructing a JFrame, setting its size, and mak-
ing it visible. Getting Started
Create the class Sis (short for Student Information System).
package sis.ui;
import javax.swing.*;
The definition for Sis includes a main method to allow you to execute the
application from the command line. About the only things you ever want to
do in a main method are to construct a new instance and call a single method
on it. You may first need to call a method to parse the command-line argu-
ments. If your main method is more involved, you need to refactor. Resist
adding any real logic to main—main is usually not comprehensively tested.
Compile and execute Sis. You should see a simple frame window (see Fig-
ure 1). Experiment with this window and note that you can do things such as
size it, minimize it, or close it like any other windowed application. You may
also want to experiment with the setSize and setVisible messages to see how
they impact the frame. Try omitting either or both message sends.
Getting Started
The only problem is that when you close this window, the Java process
does not terminate. Behind the scenes, Swing creates user threads. Exiting the
main method does not result in the termination of these user threads. See the
section Shutting Down in Lesson 13 for a discussion of user threads and dae-
mon threads.
You can tell the JFrame to terminate the Java process by telling it to exit
on close:
package sis.ui;
import javax.swing.*;
void show() {
JFrame frame = new JFrame();
frame.setSize(WIDTH, HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Now that you understand what it takes to build a frame, you can toss this
code away and write a couple of tests. You will want to refer to the Java API
documentation to see what kinds of queries you can send to a JFrame object.
The tests show that you can inspect each piece of information you use to ini-
tialize the frame. You can also query whether or not the frame is visible.
package sis.ui;
import junit.framework.*;
import javax.swing.*;
The code that meets this test ends up slightly different than the spike code:
package sis.ui;
import javax.swing.*;
Sis() {
frame.setSize(WIDTH, HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
void show() {
frame.setVisible(true);
}
JFrame getFrame() {
return frame;
}
}
When you run the test, you will see the frame window display on screen,
because the test instantiates Sis. Resist embedding such tests that pop up win-
dows. It is very distracting and will slow down your tests considerably. You
will learn techniques later that eliminate the need to render the user interface
while running tests. For now, a test or two that pops up windows might be
considered acceptable.
The frame window does not disappear even after the test completes. This
is a bigger distraction that you can fix. In your test’s tearDown method, tell the
application to close:
package sis.ui;
import javax.swing.*;
For the example, you will initially code the view portion of the Sis applica-
tion. Per the Single-Responsibility Principle, the only code you should have in
a view class is code responsible for displaying and arranging the user inter-
face. A significant benefit of organizing the code this way is that you can exe-
cute the panel as a stand-alone application. This allows you to concentrate
on developing the look, or layout, of the user interface.
In contrast, many Swing applications do not follow this rule. The view
class in these applications is cluttered with other interactions. For example,
the view class might send a message to a model class that in turn accesses
data from the database. It is virtually impossible to display the view in isola-
tion—that is, without executing the entire application. This design results in
a significantly decreased rate of layout development. Often, you can access
the view in question only by traversing a number of other application
screens.
Panels
A JFrame envelopes a content pane—a place where you put visual content. A
content pane object is a container of type java.awt.Container. You add other
components to a container, including things such as list boxes, text, and con-
tainers themselves. In Swing, the workhorse container class is JPanel. Like
JFrame, the JPanel class is a view class, used only for display purposes.
You may have noticed the package and naming convention. Swing compo-
Panels nents all begin with the letter J (JFrame, JPanel, JList, and so on); their con-
taining package is javax.swing or a subpackage of javax.swing. The package
java.awt (or a subpackage thereof) contains AWT classes. AWT class names
do not use a prefix.
Your first piece of relevant user interface will be to display the text
“Courses:” to the end user. You could directly embed this text in the JFrame’s
content pane. A better approach is to build up a second container consisting
of the text, then embed this container in the content pane. To do so, you will
create a JPanel subclass that displays text to the end user. You will then
embed this JPanel in the JFrame’s content pane.
Here is the spike for the JPanel:
package sis.ui;
import javax.swing.*;
import java.awt.*;
PANELS 573
public CoursesPanel() {
JLabel label = new JLabel("Courses:");
add(label);
}
}
The spike uses a main method containing a bit of “driver” code to allow
you to manually test the panel. If you were to code a second panel class, you
would be compelled to refactor this driver code into a common utility class.
Within the constructor of CoursesPanel, you create a new JLabel object. A
JLabel is a widget that displays text to an end user. You specify the text via
the constructor of JLabel. In order for the JLabel to be displayed, you must
add it to the JPanel using the add method.
CoursesPanel does not define add, nor does the JPanel class from which
CoursesPanel extends. You must traverse the inheritance hierarchy up to the
class java.awt.Container in order to find the definition for add. It makes sense
for add to belong there: You add other components, including JPanel objects, to
Panels
Container objects.
Following is the inheritance hierarchy for CoursesPanel.
Object
|
|—java.awt.Component
|
|—java.awt.Container
|
|—javax.swing.JComponent
|
|—javax.swing.JPanel
|
|—sis.ui.CoursesPanel
Figure 2 A JPanel
package sis.ui;
import junit.framework.*;
import javax.swing.*;
import java.awt.*;
The job of the method getLabel is to take as its parameter a JPanel and re-
turn the first JLabel object encountered within the JPanel. Since a JPanel is a
Container, you can ask it for the list of components it contains using the
getComponents message. Each element contained within the list is of type
java.awt.Component. You must test the type of each element using instanceof
to determine if it is a JLabel. You must also cast the matching element in
order to return it as a JLabel type.
PANELS 575
The use of instanceof suggests that perhaps a better approach works to de-
termine if a component exists. For now, the technique will suffice until you
have to test for a second type of component—or a second label.
Is this test necessary? The answer is debatable. You will always manually
execute a Swing application from time to time to take a look, even if you
were to write thousands of automated tests. In doing so, you will quickly de-
termine whether or not a specific component appears.
One could also ask the opposite question: Is this test sufficient? Not only could
you test that the widget appeared in the window but you could also test that it
was presented in the appropriate font and appeared at the correct coordinates.
My view is that the test may be mild overkill, but it’s not a difficult test to
write. I would rather expend the effort than find out the hard way that some-
one accidentally changed the JLabel text in one of 200 screens. Positioning-
based layout tests (tests that prove where the label appears onscreen) are
another matter. They are notoriously difficult to write and even more diffi-
cult to maintain. A bit of aesthetic distress is not usually a barrier to success-
ful application execution.
More important is that you write tests for any dynamic user interface ca-
pabilities. For example, you might allow the application to change the color
of text when the user presses a button. You will want to write a test to ensure
that this action and reaction works as you expect.
The CoursesPanel class differs from the spike only in that it defines a class
constant representing the label text.
package sis.ui;
In the Sis application, you want CoursesPanel to represent the main view.
The modified SisTest uses a similar mechanism to that in CoursesPanelTest to
prove that the content pane contains a CoursesPanel instance.
package sis.ui;
import junit.framework.*;
import javax.swing.*;
import java.awt.*;
576 SWING, PART 1
You’re already familiar with how to get a frame to include and show a
panel. You wrote virtually the same code in the “driver” for CoursesPanel.
package sis.ui;
import javax.swing.*;
Refactoring
In the course of building this example for the book, I coded the Sis construc-
tor to include the statement that makes the frame visible. Michael Feathers
reminded me that constructors are for initialization only. It’s a bit of a
stretch, I countered, but you could consider displaying the window as part of
its initialization. To which he replied, using wisdom from Ron Jeffries, that a
constructor has a lot of nerve doing something it isn’t asked to do.
REFACTORING 577
I listened to the voices of these two and fixed the deficiency. However, I
still consider that GUI widget and layout construction is simple initialization.
Putting panels within a frame is layout initialization. While you could con-
ceivably separate this layout initialization from object initialization, there is
little value in doing so. A JPanel subclass with uninitialized contents is of lit-
tle use. Forcing clients to make an additional call to initialize its layout is
redundant.
Separating the setVisible statement from the constructor is of potential
value, however. In the case of SisTest, doing so allowed for a separate initial-
ization test that doesn’t force the frame to be rendered. In production sys-
tems, it’s often valuable to initialize a frame behind the scenes, later
controlling its visibility separately.
Ultimately the decision is somewhat arbitrary and thus academic. With re-
gards to CoursesPanel, a future requirement may require a developer to cre-
ate a CoursesPanel subclass. Putting initialization code in a separate method
makes it easier to override or extend the initialization functionality. Yet you
want to avoid designing for future what-ifs (that often never come). It’s just
as easy to wait and make the change down the road—if necessary.
An acceptable compromise is to consider simple design rule #3 and extract
initialization to a private method for readability purposes:
public CoursesPanel() {
createLayout();
}
return component;
return null;
}
}
// in CoursesPanel:
...
public class CoursesPanel extends JPanel {
static final String LABEL_NAME = "coursesLabel";
...
private void createLayout() {
JLabel label = new JLabel(LABEL_TEXT);
label.setName(LABEL_NAME);
add(label);
}
}
At this point, the getComponent methods are different only by the first line.
For lack of a better place, move them into a new class as class methods and
refactor. Here is the code for the resulting class, sis.ui.Util, as well as for the
modified test classes:
// sis.ui.Util
package sis.ui;
import java.awt.*;
import javax.swing.*;
class Util {
static Component getComponent(Container container, String name) {
for (Component component: container.getComponents())
if (name.equals(component.getName()))
return component; Refactoring
return null;
}
// sis.ui.CoursesPanelTest
public void testCreate() {
CoursesPanel panel = new CoursesPanel();
580 SWING, PART 1
JLabel label =
(JLabel)Util.getComponent(panel, CoursesPanel.LABEL_NAME);
assertEquals(CoursesPanel.LABEL_TEXT, label.getText());
}
The new method calls are a bit more unwieldy. One reason is the new need
to cast. You’ll refactor when or if casting duplication becomes apparent. But
the short-term solution is a vast improvement—you’ve eliminated method-
level duplication that otherwise would become rampant in short time.
More Widgets
The SIS application should allow users to add new courses. To sup-
port this, CoursesPanel can contain a couple of entry fields in which
the user may type the course department and course number. The
user should be able to click an “Add” button that adds a new course, created
using the entered department and number, to the list.
The test for the view:
package sis.ui;
import junit.framework.*;
import javax.swing.*;
import java.awt.*;
import static sis.ui.CoursesPanel.*;
JList list =
(JList)Util.getComponent(panel, COURSES_LIST_NAME);
assertEquals(0, list.getModel().getSize());
JButton button =
(JButton)Util.getComponent(panel, ADD_BUTTON_NAME);
assertEquals(ADD_BUTTON_TEXT, button.getText());
JLabel departmentLabel =
(JLabel)Util.getComponent(panel, DEPARTMENT_LABEL_NAME);
assertEquals(DEPARTMENT_LABEL_TEXT, departmentLabel.getText());
JTextField departmentField =
(JTextField)Util.getComponent(panel, DEPARTMENT_FIELD_NAME);
assertEquals("", departmentField.getText());
MORE WIDGETS 581
JLabel numberLabel =
(JLabel)Util.getComponent(panel, NUMBER_LABEL_NAME);
assertEquals(NUMBER_LABEL_TEXT, numberLabel.getText());
JTextField numberField =
(JTextField)Util.getComponent(panel, NUMBER_FIELD_NAME);
assertEquals("", numberField.getText());
}
}
package sis.ui;
import javax.swing.*;
import java.awt.*;
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(panel); // 1
frame.setVisible(true);
}
public CoursesPanel() {
setName(NAME);
createLayout();
}
The tests should pass. You should see a window similar to that in Figure 3
when you compile and execute the view class.
The user interface is a mess. You’ll rectify this in the upcoming section en-
titled Layout. Also, nothing that would seem to be a JList appears in the win-
dow. One reason is that you have added no elements to the list—it is empty.
You’ll fix this in time as well.
Even with only seven widgets, you’ve written a good amount of tedious code
to test and build the user interface. Let’s see what we can do to tighten it up.
REFACTORING 583
Figure 3
Refactoring
The getComponent method calls require casts. For example:
JTextField numberField =
(JTextField)Util.getComponent(panel, NUMBER_FIELD_NAME);
Since you must retrieve two text fields from the panel, you have two lines
of code with the same cast. This is a form of duplication that you can elimi-
nate, using convenience methods that both eliminate the cast and make the Refactoring
code clearer.
The separate utility class Util contains the getComponent method. However, it
is perhaps more appropriate for CoursesPanel to have this responsibility,
since a panel is a container of components.
The following listing shows a highly refactored CoursesPanelTest. Some of
the changes to CoursesPanelTest will necessitate changes in CoursesPanel—
see the listing after this one.
package sis.ui;
import junit.framework.*;
import javax.swing.*;
import java.awt.*;
import static sis.ui.CoursesPanel.*;
Refactoring
While some of these refactorings eliminate duplication, technically not all
of them do. However, those refactorings that do not eliminate duplication
help improve readability. First, even though only one list needs to be verified,
creating assertEmptyList more clearly states the intent. Second, extracting assert-
ButtonText allows the entire body of testCreate to contain single-line, simple, con-
sistently phrased assertion statements.
Each of the new assertion methods contains two implicit tests. First, if no
component with the given name exists within the panel, a NullPointerExcep-
tion will be thrown, failing the test. Second, if the component is not of the
expected type, a ClassCastException will be thrown, also failing the test. If
this implicitness bothers you, feel free to add additional assertions (e.g. assert-
NotNull), but I don’t view them as necessary.
The new assertion methods are very general purpose. When (or if) you
add a second panel to the SIS application, you can refactor the assertion
methods to a common test utility class. You might choose to move them to a
junit.framework.TestCase subclass that all panel test classes inherit.
REFACTORING 585
In most systems, Swing code and associated test code is excessive and
repetitive. Take the time up front to do these refactorings. You will signifi-
cantly minimize the amount of redundancy and overall code in your system.
You can refactor component creation in CoursesPanel in a similar manner.
The following listing shows new component creation methods in addition to
the get methods required by the test.
package sis.ui;
import javax.swing.*;
import java.awt.*;
add(coursesLabel);
add(coursesList);
add(addButton);
add(departmentLabel);
add(departmentField);
add(numberLabel);
add(numberField);
}
list.setName(name);
return list;
}
Button Blicks
and
Actionlisteners
Button Clicks and ActionListeners
When you click a Swing button, some action should take place. When you
click on the Add button in CoursesPanel, you want a new course to show up
in the list of courses. Code to accomplish this will require three steps:
1. Read the contents of the course department and course number text
fields.
2. Create a new Course object using the course department and course
number text.
3. Put the Course object in the model for the courses list.
Adding a new course with these steps is a mixture of user interface respon-
sibility and business logic. Remember, you want CoursesPanel to be a more
or less dumb class that just shows information to the user. Clicking a button
BUTTON CLICKS AND ACTIONLISTENERS 587
is a controller event, one that you can respond to with an action. The details
of the action—the business logic—does not belong in CoursesPanel.
For now, you still need to write a test for the view class. Code in the panel
class still needs to tell “someone” to take action when a user clicks the Add
button. The panel test will prove that clicking on an Add button triggers
some action—an action that has yet to be defined.
You can wire a button click to an action method by using a callback. Java
supplies the interface java.awt.event.ActionListener for this purpose. To im-
plement ActionListener, you code the action method actionPerformed. Code in
actionPerformed should do whatever you want to happen when someone clicks
the button.
After defining an ActionListener class, you can assign an instance of it to
the button. When a user clicks the button, logic in JButton calls back to the
actionPerformed method on the ActionListener object.
package sis.ui;
import junit.framework.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import static sis.ui.CoursesPanel.*;
wasClicked = false;
panel.addCourseAddListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
wasClicked = true;
}
});
button.doClick();
assertTrue(wasClicked);
}
}
method gets called when the button is clicked. The JButton class provides the
method doClick to emulate a user clicking on a button.
CoursesPanel must supply a new method, addCourseAddListener. This method
simply attaches the ActionListener callback object to the JButton object.
Some production client using CoursesPanel will be responsible for defining
this callback and passing it to the view. The view remains blissfully ignorant
of any business logic.
package sis.ui;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
List Models You must change addButton to be a field and not a local variable for this to
work.
The method addCourseAddListener is a single line of code. For view code other
than layout, this is your ideal. If you find yourself putting while loops, if state-
ments, or other convoluted logic in your view class, stop! It likely contains
business or application logic that you should represent elsewhere.
List Models
You have gotten the view to represent part of the equation: Tell “someone”
to do “something” when the user clicks Add. You know that the result of
clicking Add should be that the courses panel shows a new course. You want
your view class to treat these as two discrete operations; you’ll implement
logic elsewhere to connect the two.
LIST MODELS 589
The view should allow client code to pass a Course object and, as a result,
display the course object. It doesn’t care how the Course object comes into
being. Add a new test to CoursesPanelTest:
package sis.ui;
import junit.framework.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import sis.studentinfo.*;
import static sis.ui.CoursesPanel.*;
The test sends the addCourse message to the CoursePanel. It then extracts the
underlying model of the JList. A list model is a collection class that notifies
the JList when something in the collection changes.
The JList view needs to show a useful representation of the objects con-
tained in the model. To do so, JList code sends the toString message to each
object in the model. The final line in the test asserts that the printable repre- List Models
sentation of the first object in the model is the concatenated department and
course number.
package sis.ui;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import sis.studentinfo.*;
}
...
void addCourse(Course course) {
coursesModel.addElement(course);
}
...
private JList createList(String name, ListModel model) {
JList list = new JList(model);
list.setName(name);
return list;
}
}
Use of toString
I mentioned in Lesson 9 that you should not depend upon the toString method
for production code. A toString method is generally more useful for developer-
related debugging activities. A developer might need to change Course output
from the form:
List Models
ENGL 101
to something like:
[Course:ENGL,101]
The modified string would be inappropriate for the CoursesPanel user in-
terface view. Another conflict can arise if two different views must show
Course information in different formats.
In either case, you can and should introduce a display adapter class that
wraps each course and provides the toString definition needed. You store these
adapter objects in the JList model.
The user now wants to see a hyphen between each department
and course number in the list. Modify the test to require this new
display format:
THE APPLICATION 591
import sis.studentinfo.*;
@Override
public String toString() {
return String.format(
"%s-%s", getDepartment(), getNumber());
}
}
The Application
Now that the view is in place, you can specify how the application should
use it. It’s time to tie things together.2 Here’s a new SisTest method,
testAddCourse.
2
Depending on your mindset, you might have found it easier to have started building
from the application and driven down into the view. Even while I was constructing
the view, I had in mind how I wanted the application to tie things together.
592 SWING, PART 1
package sis.ui;
import junit.framework.*;
import javax.swing.*;
import java.awt.*;
import sis.studentinfo.*;
The test drives the application from the perspective of an end user. It’s al-
most an acceptance test.
First, the test sets values into the department and course number fields. It
then emulates a click of the Add button using the button method click. In
order to ensure that the application is behaving correctly, the tests asks the
embedded CoursesPanel to return the first Course in its list.
You’ll need to add a couple of methods to CoursesPanel to support the
The Application
test:
package sis.ui;
...
public class CoursesPanel extends JPanel {
...
Course getCourse(int index) {
Course adapter =
(CourseDisplayAdapter)coursesModel.getElementAt(index);
return adapter;
}
...
void setText(String textFieldName, String text) {
getField(textFieldName).setText(text);
}
}
import javax.swing.*;
import java.awt.event.*;
import sis.studentinfo.*;
public Sis() {
initialize();
}
frame.setSize(WIDTH, HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(panel);
}
...
void createCoursesPanel() {
panel = new CoursesPanel();
panel.addCourseAddListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
addCourse();
}
}
); The Application
}
The Sis class ties together the action listener and the ability to add a course
to the panel. Most of the code should look familiar—it is the client code you
built in tests for CoursesPanel.
You’ll need to add the getText method to CoursesPanel:
String getText(String textFieldName) {
return getField(textFieldName).getText();
}
594 SWING, PART 1
Figure 4
You can now run Sis as a stand-alone application and experiment with
adding courses. The screen shot in Figure 4 shows Sis after a user has entered
five courses. It’s still a mess!
Layout
The user interface for the Sis application is so poorly laid out that it’s confus-
Layout ing to the end user. The problem is that by default, Swing lays out compo-
nents to flow from left to right, in the order you add them to a container.
When insufficient room remains to place a component on a “line,” Swing
wraps it, just like a word in a word processor. The component appears to the
left side of the panel below the current line.
In Figure 4, the first line consists of four widgets: the “Courses” label, the
list of courses, the Add button, and the “Department” label. The second line
consists of the department field, the “Number” label, and the course number
field.
Resize the window and make it as wide as your screen. Swing will redraw
the widgets. If your screen is wide enough, all of the components should flow
from left to right on a single line.
Swing provides several alternate layout mechanisms, each in a separate
class, to help you produce aesthetically pleasing user interfaces. Swing refers
to these classes as layout managers. You can associate a different layout man-
LAYOUT 595
GridLayout
You’ll start with a simply understood but usually inappropriate layout, Grid-
Layout. A GridLayout divides the container into equal-sized rectangles based
on the number of rows and columns you specify. As you add components to
the container, the GridLayout puts each in a cell (rectangle), moving from left
to right, top to bottom (by default). The layout manager resizes each compo-
nent to fit its cell.
Make the following changes in CoursesPanel:
int rows = 4;
int cols = 2;
setLayout(new GridLayout(rows, cols));
596 SWING, PART 1
add(coursesLabel);
add(coursesList);
add(addButton);
add(new JPanel());
add(departmentLabel);
add(departmentField);
add(numberLabel);
add(numberField);
}
You assign a layout manager to the panel by sending it the message setLay-
out. In createLayout, you send the message setLayout to the CoursesPanel object,
passing it a GridLayout instance.
The result of executing CoursesPanel using the GridLayout is shown in
Figure 5.
The coursesLabel ends up in the upper left rectangle. The coursesList, the sec-
ond component to be added, is in the upper right rectangle. The Add button
drops down to the second row of rectangles, and is followed by an empty
JPanel to fill the next rectangle. Each of the final two rows displays a label
and its corresponding field.
Since each rectangle must be the same size, GridLayout doesn’t have a lot
of applicability in organizing “typical” interfaces with lots of buttons, fields,
lists, and labels. It does have applicability if, for example, you are presenting
a suite of icons to the end user. GridLayout does contain additional methods
to improve upon its look, but you will usually want to use a more-
sophisticated layout manager.
Layout
Figure 5
LAYOUT 597
BorderLayout
BorderLayout is a simple but effective layout manager. BorderLayout allows
you to place up to five components within the container: one each at the
compass points north, east, south, and west and one to fill the remainder, or
center (see Figure 6).
For CoursesPanel, you will replace the GridLayout with a BorderLayout.
Your BorderLayout will use three of the available areas: north, to contain the
“Courses” Label; center, to contain the list of courses; and south, to contain
the remainder of the widgets. You will organize the southern, or “bottom,”
widgets in a subpanel that uses a separate layout manager.
The createLayout method already is overly long, at close to 30 lines. Courses-
Panel is a simple interface so far. Imagine a sophisticated panel with several
dozen widgets. Unfortunately, it is common to encounter Swing code that
does all of the requisite initialization and layout in a single method. Develop-
ers create panels within panels, they create and initialize components and
place them in panels, they create layouts, and so on.
A more effective code composition is to extract the creation of each panel
into a separate method. This not only makes the code far more readable but
also provides more flexibility in rearranging the layout. I’ve refactored the
code in CoursesPanel to reflect this cleaner organization.
North
North
setLayout(new BorderLayout());
add(coursesLabel, BorderLayout.NORTH);
add(coursesList, BorderLayout.CENTER);
add(createBottomPanel(), BorderLayout.SOUTH);
}
JPanel createBottomPanel() {
addButton = createButton(ADD_BUTTON_NAME, ADD_BUTTON_TEXT);
panel.add(addButton, BorderLayout.NORTH);
panel.add(createFieldsPanel(), BorderLayout.SOUTH);
return panel;
}
JPanel createFieldsPanel() {
int columns = 20;
JLabel departmentLabel =
createLabel(DEPARTMENT_LABEL_NAME, DEPARTMENT_LABEL_TEXT);
JTextField departmentField =
createField(DEPARTMENT_FIELD_NAME, columns);
JLabel numberLabel =
createLabel(NUMBER_LABEL_NAME, NUMBER_LABEL_TEXT);
JTextField numberField =
createField(NUMBER_FIELD_NAME, columns);
int rows = 2;
int cols = 2;
panel.add(numberLabel);
panel.add(numberField);
return panel;
}
The code in the CoursesPanel constructor sets its layout to a new instance
of BorderLayout. It puts the label on the north (top) side of the panel and the
list in the center of the panel. It puts the result of the method createBottomPanel,
another JPanel, on the south (bottom) side of the panel (see Figure 7) The
benefit of putting the list in the center is that it will expand as the frame win-
dow expands. The other widgets retain their original size.
LAYOUT 599
Figure 7
A Test Problem
If you rerun your tests, you now get three NullPointerException errors. How
can that be, since you neither changed logic nor added/removed any compo-
Layout
nents?
When you investigate the stack trace for the NullPointerException, you
should discover that some of the get methods to extract a component from a
container are failing. The problem is that the Util method getComponent only
looks at components directly embedded within a container. Your layout code
now embeds containers within containers (JPanels within JPanels). The code
in getComponent ignores Components that have been added to subpanels.
The Util class doesn’t have any tests associated with it. At this point, to
help fix the problem and enhance your test coverage, you need to add appro-
priate tests. UtilTest contains three tests that should cover most expected cir-
cumstances:
package sis.ui;
import junit.framework.*;
import javax.swing.*;
import java.awt.*;
600 SWING, PART 1
panel.add(subpanel);
Layout
The third test, testSubcomponent, should fail for the same reason your other
tests are failing. To fix the problem, you will need to modify getComponent. For
each component in a container, you will need to determine whether or not
that component is a container (using instanceof). If so, you will need to tra-
verse all of that subcontainer’s components, repeating the same process for
each. The most effective way to accomplish this is to make recursive calls to
getComponent.
if (subcomponent != null)
return subcomponent;
}
}
return null;
}
BoxLayout
The BoxLayout class allows you to lay your components out on either a hor-
izontal or vertical axis. Components are not wrapped when you resize the
container; also, components do not grow to fill any area. The bottom panel,
which must position an Add button and the fields subpanel vertically, one
atop the other, is an ideal candidate for BoxLayout.
JPanel createBottomPanel() {
addButton = createButton(ADD_BUTTON_NAME, ADD_BUTTON_TEXT);
panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
Layout
return panel;
}
You must pass an instance of the panel to the constructor of BoxPanel and
a constant indicating the direction in which to lay out components. The con-
stant PAGE_AXIS by default represents a top-to-bottom, or vertical, orientation.
The other option is LINE_AXIS, which represents horizontal orientation by de-
fault.4
You can create invisible “rigid areas” to separate components with white-
space. These rigid areas retain a fixed size even when the container is resized.
4
You can change the orientation by sending applyComponentOrientation to the container.
Older versions of BoxLayout supported only explicit X_AXIS and Y_AXIS constants.
The newer constants allow for dynamic reorganization, perhaps to support interna-
tionalization needs.
602 SWING, PART 1
GridBagLayout
To fine-tune a layout, you will be forced to work with GridBagLayout, a
highly configurable but more complex layout manager. GridBagLayout is
similar to GridLayout in that it lets you organize components in a grid of rec-
tangles. However, GridBagLayout gives you far more control. First, each rec-
tangle is not fixed in size—it is generally sized according to the default, or
“preferred” size of the component it contains. Components can span multi-
ple rows or columns.
GRIDBAGLAYOUT 603
The modified createFieldsPanel code shows how to lay out labels and fields
using a GridBagLayout. (The code in the method presumes that you have sta-
tically imported java.awt.GridBagConstraints.*.)
JPanel createFieldsPanel() {
GridBagLayout layout = new GridBagLayout();
layout.setConstraints(departmentLabel,
new GridBagConstraints(
0, 0, // x, y
1, 1, // gridwidth, gridheight
40, 1, // weightx, weighty
LINE_END, //anchor
NONE, // fill
new Insets(3, 3, 3, 3), // top-left-bottom-right
0, 0)); // padx, ipady
layout.setConstraints(departmentField,
new GridBagConstraints(1, 0, 2, 1, 60, 1,
CENTER, HORIZONTAL,
new Insets(3, 3, 3, 3), 0, 0));
layout.setConstraints(numberLabel,
new GridBagConstraints(0, 1, 1, 1, 40, 1, GridBagLayout
LINE_END, NONE,
new Insets(3, 3, 3, 3), 0, 0));
layout.setConstraints(numberField,
new GridBagConstraints(1, 1, 2, 1, 60, 1,
CENTER, HORIZONTAL,
new Insets(3, 3, 3, 3), 0, 0));
panel.add(departmentLabel);
panel.add(departmentField);
panel.add(numberLabel);
panel.add(numberField);
return panel;
}
After creating a GridBagLayout and setting it into the panel, you call the
method setConstraints on the layout for each widget to add. The setConstraints
method takes two parameters: a Component object and a GridBagConstraints
604 SWING, PART 1
gridx/gridy The cell at which to begin drawing the component. The upper
left cell is 0, 0.
fill The fill constraint specifies how the component should grow to
fill the display area if a component is smaller than its display
area. Values are NONE (don’t resize), HORIZONTAL, VERTICAL, and
BOTH.
ipadx/ipady Specifies how much space to add to the minimum size of a com-
ponent.
GridBagLayout
JPanel createFieldsPanel() {
GridBagLayout layout = new GridBagLayout();
addField(panel, layout, 0,
DEPARTMENT_LABEL_NAME, DEPARTMENT_LABEL_TEXT,
DEPARTMENT_FIELD_NAME, columns);
addField(panel, layout, 1,
NUMBER_LABEL_NAME, NUMBER_LABEL_TEXT,
NUMBER_FIELD_NAME, columns);
return panel;
}
panel.add(label);
panel.add(field);
}
The onerous nature of such code should drive you in the direction of ex-
treme refactoring. Consider replacing repetition in the construction of Grid-
BagConstraints objects by using a simplified utility constructor. If you need
to represent more than a couple of fields and associated labels, consider rep-
resenting each pair using a data class. You can then represent the entire set of
fields in a table, iterating through it to create the layout.
Figure 9 shows a layout that is getting close to being acceptable. Resize it
to see how the field components fill to their display area.
606 SWING, PART 1
As of Java 1.4, Sun introduced the SpringLayout class. This layout manager
is primarily designed for the use of GUI composition tools. The basic concept
of SpringLayout is that you define a layout by tying the edges of components
together using constraints known as springs.
In CoursesPanel, you might create a spring to attach the west (left) edge of
the department text field to the east (right) edge of the department label. The
spring object attaching the two components is a fixed five pixels in size. An-
other spring might attach the east edge of the department text field to the
east side of the panel itself, also separated by a spring of fixed length. As the
panel grows in width, the department text field would grow accordingly.
Moving Forward Creating a SpringLayout by hand is fairly easy for a panel with only a few
components. It can also be incredibly frustrating and difficult for a more
complex layout. In most cases, you will want to leave the job to a layout
tool.
Moving Forward
You’ve only scratched the surface of Swing! In the next chapter, you’ll tighten
up the look and the feel of CoursesPanel with some fine-tuning. I’ll then run
through a few more generally useful Swing topics.
Additional Lesson II
Swing, Part 2
In the last lesson you learned how to build a basic Swing application,
using panels, labels, buttons, text fields, and lists. You also learned how to
use Swing’s layout managers for enhancing the look of your user interface
(UI).
In this chapter, you will learn about:
• Scroll panes
• Borders
• Setting text in the title bar
• Icons
• Keyboard support
• Button mnemonics
• Required fields
• Keyboard listeners
• The Swing Robot class Swing, Part 2
• Field edits and document filters
• Formatted text fields
• Tables
• Mouse listeners
• Cursors
• SwingUtilities methods invokeAndWait and invokeLater
607
608 SWING, PART 2
Miscellaneous Aesthetics
In this section you’ll learn how to enhance the look of the existing Courses-
Panel.
JScrollPane
If you add more than a few courses using sis.ui.Sis, you’ll note that the JList
shows only some of them. To fix this problem, you can wrap the JList in a
scroll pane. The scroll pane acts as a viewport onto the list. When the list’s
model contains more information than can be displayed given the current
JList size, the scroll pane draws scroll bars. The scroll bars allow you to move
the viewport either horizontally or vertically to reveal hidden information.
The bold code in this listing for the CoursesPanel adds a scroll pane. You
can specify whether you want to show scroll bars always or only as needed
using either setVerticalScrollBarPolicy or setHorizontalScrollBarPolicy. I find it more
aesthetically pleasing to always show the vertical scroll bar.
setLayout(new BorderLayout());
add(coursesLabel, BorderLayout.NORTH);
add(coursesScroll, BorderLayout.CENTER);
Miscellaneous add(createBottomPanel(), BorderLayout.SOUTH);
Aesthetics }
Borders
The courses list and associated labels directly abut the edge of the panel. You
can use a border to create a buffer area between the edges of the panel and
any of its components.
coursesScroll.setVerticalScrollBarPolicy(
ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
setLayout(new BorderLayout());
add(coursesLabel, BorderLayout.NORTH);
add(coursesScroll, BorderLayout.CENTER);
add(createBottomPanel(), BorderLayout.SOUTH);
}
You can use the BorderFactory class to create one of several different bor-
der types. Most of the borders are for decorative purposes; the empty border
is the exception. You can also combine borders using createCompoundBorder. The
reworked createLayout method demonstrates the use of a few different border
types.
setLayout(new BorderLayout());
The use of a titled border eliminates the need for a separate JLabel to rep-
resent the “Courses:” text. The elimination of the JLabel will break testCreate
in CoursesPanelTest—did you remember to make this change by testing
first?
Adding a Title
No text appears in the SIS frame window’s titlebar. Rectify this by updating
testCreate in SisTest:
610 SWING, PART 2
JFrame supplies constructors that allow you to pass in titlebar text. The code
in Sis:
public class Sis {
...
static final String COURSES_TITLE = "Course Listing”;
private JFrame frame = new JFrame(COURSES_TITLE);
...
}
Icons
The final aesthetic element you’ll add is an icon for the window. By default,
you get the cup-o-Java icon, which appears as a mini-icon in the titlebar and
when you minimize the window. Since the icon is part of the titlebar, it falls
under the control of the frame window.
A test can simply ask for the icon from a frame by sending it the message
getIconImage. The method, implemented in java.awt.Frame, returns an object of
type java.awt.Image. The code in testCreate asserts that the icon retrieved from
the SIS frame is the same as one explicitly loaded by name. Both the test
and the sis.ui.Sis code will use a common utility method to load the image:
ImageUtil.create.
Create the class ImageUtilTest in the sis.util package. There are several
ways to write a test against the create method. The best way would be to dy-
namically generate an image using a collection of pixels and write it out to
MISCELLANEOUS AESTHETICS 611
disk. After the create method loaded the image, you could assert that the pix-
els in the loaded image were the same as the original pixel collection. Unfor-
tunately, this is an involved solution, one that is best solved using an API that
eases the dynamic creation of images.
A simpler technique is to allow the test to assume that a proper image al-
ready exists on disk under a recognized filename. The test can then simply as-
sert that the image load was successful by ensuring that the loaded image is
not null. The image must appear on the classpath.1
package sis.util;
import junit.framework.*;
import java.awt.*;
This may seem weak, but it will provide an effective solution for now. You
must ensure that the image courses.gif continues to stay in existence through-
out the lifetime of the project. Instead of using an image file associcated with
the SIS project, you might consider creating an image explicitly used for test-
ing purposes.
Java provides several different ways to load and manipulate images. You
will use the most direct.
package sis.util;
import javax.swing.*;
import java.awt.*;
Miscellaneous
public class ImageUtil {
Aesthetics
public static Image create(String path) {
java.net.URL imageURL = ImageUtil.class.getResource(path);
if (imageURL == null)
return null;
return new ImageIcon(imageURL).getImage();
}
}
1
I’ve modified the build.xml Ant script for this section to copy any file in src/images
into classes/images each time a compile takes place. This allows you to quickly re-
move the entire contents of the classes directory without having to worry about re-
taining any images.
612 SWING, PART 2
The class Class defines the method getResource. This method allows you to
locate resources, including files, regardless of whether the application is
loaded from a JAR file, individual class files, or some other source such as
the Internet.2 The result of calling getResource is a URL—the unique address of
the resource.
Once you have a proper URL, you can pass it to the ImageIcon construc-
tor to obtain an ImageIcon object. You can use ImageIcon objects for various
purposes, such as decorating buttons or labels. Since the Frame class requires
an Image object, not an ImageIcon, here you use the getImage method to pro-
duce the return value for create.
Note that the image filename passed to getResource is /images/courses.gif—a
path that uses a leading slash. This indicates that the resource should be lo-
cated starting from the root of each classpath entry. Thus, if you execute
classes from the directory c:\swing2\classes, you should put courses.gif in
c:\swing2\classes\images. If you execute classes by loading them from a JAR file, it
should contain courses.gif with a relative path of images.
Here are the changes to the initialize method in the Sis class.
private void initialize() {
createCoursesPanel();
frame.setSize(WIDTH, HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(panel);
}
Feel
Feel
The visual appearance of the interface is important, but more important is its
“feel.” The feel of an application is what a user experiences as he or she in-
teracts with it. Examples of elements relevant to the feel of an application
include:
2
Technically, the image is loaded using the class loader as that of the Class object on
which you call getResource. For applications that do not use a custom class loader,
using the class loader of the ImageUtil class will work just fine.
FEEL 613
• ability to effect all behaviors using either the keyboard or the mouse
• tabbing sequence—can the user visit all text fields in the proper order
and are irrelevant items omitted from the tab sequence? Feel
• field limits—is the user restricted from entering too many characters?
• field constraints—is the user restricted from entering inappropriate data
into a field?
• button activation—are buttons deactivated when their use is inappro-
priate?
It is possible to address both the look and feel at the same time. In the last
section, you decorated the list of courses with a scroll pane. This improved
the look of the interface and at the same time provided the “feel” to allow a
user to scroll the list of courses when necessary.
614 SWING, PART 2
Keyboard Support
One of the primary rules of GUIs is that a user must be able to completely
control the application using either the keyboard or the mouse. Exceptions
exist. Using the keyboard to draw a polygon is ineffective, as is entering char-
acters using the mouse. (Both are of course possible.) In such cases, the appli-
cation developer often chooses to bypass the rule. But in most cases it is
extremely inconsiderate to ignore the needs of a keyboard-only or mouse-
only user.
By default, Java supplies most of the necessary support for dual keyboard
and mouse control. For example, you can activate a button by either clicking
on it with the mouse or tabbing to it and pressing the space bar.
Button Mnemonics
An additional way you can activate a button is using an Alt-key combina-
tion. You combine pressing the Alt key with another key. The other key is
typically a letter or number that appears in the button text. You refer to this
key as a mnemonic (technically, a device to help you remember something; in
Java, it’s simply a single-letter shortcut). An appropriate mnemonic for the
Add button is the letter A.
The mnemonic is a view class element. The specifications for the
mnemonic remain constant with the view’s appearance. Therefore the more
appropriate place to test and manage the mnemonic is in the view class.
In CoursesPanelTest:
public void testCreate() {
assertEmptyList(COURSES_LIST_NAME);
assertButtonText(ADD_BUTTON_NAME, ADD_BUTTON_TEXT);
assertLabelText(DEPARTMENT_LABEL_NAME, DEPARTMENT_LABEL_TEXT);
assertEmptyField(DEPARTMENT_FIELD_NAME);
Feel assertLabelText(NUMBER_LABEL_NAME, NUMBER_LABEL_TEXT);
assertEmptyField(NUMBER_FIELD_NAME);
In CoursesPanel:
public class CoursesPanel extends JPanel {
...
static final char ADD_BUTTON_MNEMONIC = 'A';
...
JPanel createBottomPanel() {
addButton = createButton(ADD_BUTTON_NAME, ADD_BUTTON_TEXT);
FEEL 615
addButton.setMnemonic(ADD_BUTTON_MNEMONIC);
...
return panel;
}
...
}
Required Fields
A valid course requires both a department and course number. However,
sis.ui.Sis allows you to omit either or both, yet still press the Add button. You
want to modify the application to disallow this circumstance.
One solution would be to wait until the user presses Add, then ensure that
both department and course number contain a nonempty string. If not,
then present a message pop-up that explains the requirement. While this so-
lution will work, it creates a more annoying user experience. Users don’t
want continual interruption from message pop-ups. A better solution is to
proactively disable the Add button until the user has entered information in
both fields.
You can monitor both fields and track when a user enters information in
them. Each time a user presses a character, you can test the field contents and
enable or disable the Add button as appropriate.
Managing enabling/disabling of the Add button is an application character-
istic, not a view characteristic. It involves business-related logic, as it is based
on the business need for specific data. As such, the test and related code be-
longs not in the panel class, but elsewhere. Feel
Another indicator that the code does not belong in the view class is that
you have interaction between two components. You want the controller to
notify other classes of an event (keys being pressed) and you want those
other classes to tell the view what to present (a disabled or enabled button)
under certain circumstances. These are two separate concerns. You don’t
want logic in the view trying to mediate things.
To solve the bigger problem, however, it is easier to provide tests against
CoursesPanel that prove each of the two smaller occurences. A first test en-
sures that a listener is notified when keys are pressed. A second test ensures
that CoursesPanel can enable and disable buttons.
Start with a test (in CoursesPanelTest) for enabling and disabling buttons:
616 SWING, PART 2
panel.setEnabled(ADD_BUTTON_NAME, false);
assertFalse(button.isEnabled());
}
The harder test is at the application level. A listener in the Sis object should
Feel
receive messages when a user types into the department and number fields.
You must prove that the listener’s receipt of these messages triggers logic to
enable/disable the Add button. You must also prove that various combinations
of text/no text in the fields results in the appropriate state for the Add button.
A bit of programming by intention in SisTest will provide you with a test
skeleton:
public void testKeyListeners() throws Exception {
sis.show();
selectField(CoursesPanel.NUMBER_FIELD_NAME);
type(‘1’);
assertTrue(button.isEnabled());
}
The test ensures that the button is disabled by default. After typing values
into the department and number fields, it verifies that the button is enabled.
The trick, of course, is how to select a field and emulate typing into it. Swing
provides a few solutions. Unfortunately, each requires you to render (make
visible) the actual screen. Thus the first line in the test is a call to the show
method of Sis.
The solution I’ll present involves use of the class java.awt.Robot. The
Robot class emulates end-user interaction using the keyboard and/or mouse.
Another solution requires you to create keyboard event objects and pass
them to the fields using a method on java.awt.Component named dispatchEvent.
You can construct a Robot object in the SisTest setUp method. (After build-
ing this example, I noted persistent use of the CoursesPanel object, so I also
refactored its extraction to setUp.)
public class SisTest extends TestCase {
...
private CoursesPanel panel;
private Robot robot;
After obtaining a field object, you can obtain its absolute position on the
screen by sending it the message getLocationOnScreen. This returns a Point ob-
ject—a coordinate in Cartesian space represented by an x and y offset.3 You
can send this coordinate as an argument to Robot’s mouseMove method.
3
The upper left corner of your screen has an (x, y) coordinate of (0, 0). The value of y
increases as you move down the screen. For example, you would express two pixels
to the right and one pixel down as (2, 1).
618 SWING, PART 2
The code in Sis adds a single listener to each text field. This listener waits
on keyReleased events. When it receives one, the listener calls the method
setAddButtonState. The code in setAddButtonState looks at the contents of the two
fields to determine whether or not to enable the Add button.
setAddButtonState();
}
Feel
void setAddButtonState() {
panel.setEnabled(CoursesPanel.ADD_BUTTON_NAME,
!isEmpty(CoursesPanel.DEPARTMENT_FIELD_NAME) &&
!isEmpty(CoursesPanel.NUMBER_FIELD_NAME));
}
Note that the last line in createKeyListeners calls setAddButtonState in order to set
the Add button to its default (initial) state.
FEEL 619
panel.setText(CoursesPanel.DEPARTMENT_FIELD_NAME, "a");
sis.setAddButtonState();
assertFalse(button.isEnabled());
panel.setText(CoursesPanel.NUMBER_FIELD_NAME, "1");
sis.setAddButtonState();
assertTrue(button.isEnabled());
panel.setText(CoursesPanel.DEPARTMENT_FIELD_NAME, "a");
panel.setText(CoursesPanel.NUMBER_FIELD_NAME, " ");
sis.setAddButtonState();
assertFalse(button.isEnabled());
}
Feel
Field Edits
When you provide an effective user interface, you want to make it as difficult
as possible for users to enter invalid data. You learned that you don’t want to
interrupt users with pop-ups when requiring fields. Similarly, you want to
avoid presenting pop-ups to tell users they have entered invalid data.
A preferred solution involves verifying and even modifying data in a text
field as the user enters it. As an example, a course department must contain
only uppercase letters. “CMSC” is a valid department, but “Cmsc” and “cmsc” are
not. To make life easier for your users, you can make the department text
field automatically convert each lowercase letter to an uppercase letter as the
user types it.
620 SWING, PART 2
The evolution of Java has included several attempts at solutions for dy-
namically editing fields. Currently, there are at least a half dozen ways to go
about it. You will learn two of the preferred techniques: using JFormatted-
TextField and creating custom DocumentFilter classes.
You create a custom filter by subclassing javax.swing.text.DocumentFilter.
In the subclass, you override definitions for any of three methods: insertString,
remove, and replace. You use these methods to restrict invalid input and/or
transform invalid input into valid input.
As a user enters or pastes characters into the text field, the insertString
method is indirectly called. The replace method gets invoked when a user first
selects existing characters in a text field before typing or pasting new charac-
ters. The remove method is invoked when the user deletes characters from the
text field. You will almost always need to define behavior for insertString and
replace, but you will need to do so only occasionally for remove.
Once you have defined the behavior for the custom filter, you attach it to a
text field’s document. The document is the underlying data model for the text
field; it is an implementation of the interface javax.swing.text.Document.
You obtain the Document object associated with a JTextField by sending it
the message getDocument. You can then attach the custom filter to the Document
using setDocumentFilter.
package sis.ui;
import javax.swing.*;
import javax.swing.text.*;
import junit.framework.*;
sentially a reference to the document that ignores any filters. After you trans-
form data in insertString, you must call insertString on the filter bypass. Other-
wise, you will create an infinite loop!
The difficulty with respect to testing is that Swing provides no direct way
to obtain a filter bypass object. You need the bypass in order to test the filter.
The solution presented above is to provide a new implementation of Doc-
umentFilter.FilterBypass. This implementation stores a concrete instance of
an AbstractDocument (which implements the Document interface) known as
a PlainDocument. To flesh out the bypass, you must supply implementations
for the three methods insertString, remove, and replace. For now, the test only re-
quires you to implement insertString.
The insertString method doesn’t need to take a bypass object as its first pa-
rameter, since it is defined in the filter itself. Its job is to call the document’s
insertString method directly (i.e., without calling back into the DocumentFil-
ter). Note that this method can throw a BadLocationException if the start
position is out of range.
Once you have a DocumentFilter.FilterBypass instance, the remainder of
the setup and test is easy. From the bypass object, you can obtain and store a
document reference. You assert that the contents of this document were ap-
propriately updated.
The test (UpcaseFilterTest) contains a lot of code. You might think that the
robot-based test would have been easier to write. In fact, it would have.
However, robots have their problems. Since they take control of the mouse
and keyboard, you have to be careful not to do anything else while the tests
execute. Otherwise you can cause the robot tests to fail. This alone is reason
to avoid them at all costs. If you must use robot tests, find a way to isolate
them and perhaps execute them at the beginning of your unit-test suite.
Also, the test code for the second filter you write will be as easy to code as
the corresponding robot test code. Both filter tests would require the document-
Feel
Text and createBypass methods as well as most of the setUp method.
import javax.swing.text.*;
int offset,
String text,
AttributeSet attr) throws BadLocationException {
bypass.insertString(offset, text.toUpperCase(), attr);
}
}
When the filter receives the insertString message, its job in this case is to con-
vert the text argument to uppercase, and pass this transformed data off to the
bypass.
Once you’ve demonstrated that your tests all still pass, you can now code
the replace method. The test modifications:
...
public class UpcaseFilterTest extends TestCase {
...
public void testReplace() throws BadLocationException {
filter.insertString(bypass, 0, "XYZ”, null);
filter.replace(bypass, 1, 2, "tc”, null);
assertEquals("XTC”, documentText());
The test shows that the replace method takes an additional argument. The
third parameter represents the number of characters to be replaced, starting
at the position represented by the second argument. The production code:
package sis.ui;
import javax.swing.text.*;
int length,
String text,
AttributeSet attr) throws BadLocationException {
bypass.replace(offset, length, text.toUpperCase(), attr);
}
}
UpcaseFilter is complete. You need not concern yourself with filtering re-
moval operations when uppercasing input.
A Second Filter
You also want to constrain the number of characters in both the department
and course number field. In fact, in most applications that require field entry,
you will want the ability to set field limits. You can create a second custom fil-
ter, LimitFilter. The following code listing shows only the production class. The
test, LimitFilterTest (see the code athttp://www.LangrSoft.com/agileJava/code/)
contains a lot of commonality with UpcaseFilterTest that you can factor out.
package sis.ui;
Note the technique of having insertString delegate to the replace method. The
other significant bit of code involves throwing a BadLocationException if the
replacement string is too large.
Building such a filter and attaching it to the course number field is easy
enough. You construct a LimitFilter by passing it the character length. For
example, the code snippet new LimitFilter(3) creates a filter that prevents more
than three characters.
The problem is that you can set only a single filter on a document. You have a
couple of choices. The first (bad) choice is to create a separate filter for each
combination. For example, you might have filter combinations Upcase-
LimitFilter and NumericOnlyLimitFilter. A better solution involves some form of
abstraction—a ChainableFilter. The ChainableFilter class subclasses Document-
Filter. It contains a sequence of individual filter classes and manages calling each
in turn. The code available at http://www.LangrSoft.com/agileJava/code/ for this
lesson demonstrates how you might build such a construct.4
Feel JFormattedTextField
Another mechanism for managing field edits is to use the class
javax.swing.JFormattedTextField, a subclass of JTextField. You can attach
formatters to the field to ensure that the contents conform to your specifica-
tion. Further, you can retrieve the contents of the field as appropriate object
types other than text.
You want to provide an effective date field for the course. This date repre-
sents when the course is first made available in the system. Users must enter
the date in the format mm/dd/yy. For example, 04/15/02 is a valid date.
4
The listing does not appear here for space reasons.
FEEL 627
JFormattedTextField dateField =
(JFormattedTextField)panel.getField(EFFECTIVE_DATE_FIELD_NAME);
DateFormatter formatter = (DateFormatter)dateField.getFormatter();
SimpleDateFormat format = (SimpleDateFormat)formatter.getFormat();
assertEquals("MM/dd/yy", format.toPattern());
assertEquals(Date.class, dateField.getValue().getClass());
}
JPanel createFieldsPanel() {
GridBagLayout layout = new GridBagLayout();
addField(panel, layout, 0,
DEPARTMENT_LABEL_NAME, DEPARTMENT_LABEL_TEXT,
createField(DEPARTMENT_FIELD_NAME, columns)); Feel
addField(panel, layout, 1,
NUMBER_LABEL_NAME, NUMBER_LABEL_TEXT,
createField(NUMBER_FIELD_NAME, columns));
5
The capital letter M is used for month, while the lowercase letter m is used for
minutes.
628 SWING, PART 2
addField(panel, layout, 2,
EFFECTIVE_DATE_LABEL_NAME, EFFECTIVE_DATE_LABEL_TEXT,
dateField);
return panel;
}
If you execute the application with these changes, you will note that the
effective date field allows you to type invalid input. When you leave the field,
it reverts to a valid value. You can override this default behavior; see the API
documentation for JFormattedTextField for the alternatives.
A design issue now exists. The code to create the formatted text field is in
CoursesPanel and the associated test is in CoursesPanelTest. This contrasts
with the goal I previously stated to manage edits at the application level!
You want to completely separate the view and application concerns. A so-
lution involves the single responsibility principle. It will also eliminate some
of the duplication and code clutter that I’ve allowed to fester in CoursesPanel
and Sis.
A Field object is a data object whose attributes describe the information
necessary to be able to create Swing text fields. A field is implementation-
neutral, however, and has no knowledge of Swing. A FieldCatalog con-
tains the collection of available fields. It can return a Field object given its
name.
The CoursesPanel class needs only contain a list of field names that it
must render. The CoursesPanel code can iterate through this list, asking a
FieldCatalog for the corresponding Field object. It can then send the Field
object to a factory, TextFieldFactory, whose job is to return a JTextField.
The factory will take information from the Field object and use it to add
various constraints on the JTextField, such as formats, filters, and length
limits.
Feel The code for the new classes follows. I also show the code in CoursesPanel
that constructs the text fields.
// FieldCatalogTest.java
package sis.ui;
import junit.framework.*;
import static sis.ui.FieldCatalog.*;
assertEquals(3, catalog.size());
FEEL 629
field = catalog.get(DEPARTMENT_FIELD_NAME);
assertEquals(DEFAULT_COLUMNS, field.getColumns());
assertEquals(DEPARTMENT_LABEL_TEXT, field.getLabel());
assertEquals(DEPARTMENT_FIELD_LIMIT, field.getLimit());
assertTrue(field.isUpcaseOnly());
field = catalog.get(EFFECTIVE_DATE_FIELD_NAME);
assertEquals(DEFAULT_COLUMNS, field.getColumns());
assertEquals(EFFECTIVE_DATE_LABEL_TEXT, field.getLabel());
assertSame(DEFAULT_DATE_FORMAT, field.getFormat());
}
}
// FieldCatalog.java
package sis.ui;
import java.util.*;
import java.text.*;
public FieldCatalog() {
loadFields();
}
put(fieldSpec);
put(fieldSpec);
put(fieldSpec);
}
// TextFieldFactoryTest.java
Feel package sis.ui;
import javax.swing.*;
import javax.swing.text.*;
import java.util.*;
import java.text.*;
import junit.framework.*;
import sis.util.*;
fieldSpec.setColumns(COLUMNS);
}
assertEquals(COLUMNS, field.getColumns());
assertEquals(FIELD_NAME, field.getName());
assertEquals(textValue, field.getText());
}
assertTrue(filters.contains(LimitFilter.class));
assertTrue(filters.contains(UpcaseFilter.class));
}
632 SWING, PART 2
JFormattedTextField field =
(JFormattedTextField)TextFieldFactory.create(fieldSpec);
assertEquals(1, field.getColumns());
assertEquals(FIELD_NAME, field.getName());
// TextFieldFactory.java
package sis.ui;
import javax.swing.*;
import javax.swing.text.*;
if (fieldSpec.getFormat() != null)
field = createFormattedTextField(fieldSpec);
Feel else {
field = new JTextField();
if (fieldSpec.getInitialValue() != null)
field.setText(fieldSpec.getInitialValue().toString());
}
if (fieldSpec.getLimit() > 0)
attachLimitFilter(field, fieldSpec.getLimit());
if (fieldSpec.isUpcaseOnly())
attachUpcaseFilter(field);
field.setColumns(fieldSpec.getColumns());
field.setName(fieldSpec.getName());
return field;
}
FEEL 633
// CoursesPanelTest.java
...
public void testCreate() {
assertEmptyList(COURSES_LIST_NAME);
assertButtonText(ADD_BUTTON_NAME, ADD_BUTTON_TEXT);
String[] fields =
{ FieldCatalog.DEPARTMENT_FIELD_NAME,
FieldCatalog.NUMBER_FIELD_NAME,
FieldCatalog.EFFECTIVE_DATE_FIELD_NAME }; Feel
assertFields(fields);
assertLabelText(fieldSpec.getLabelName(), fieldSpec.getLabel());
}
}
...
// CoursesPanel.java
...
JPanel createFieldsPanel() {
GridBagLayout layout = new GridBagLayout();
return panel;
}
Notes:
• TestUtil.assertDateEquals is a new utility method whose implementation
should be obvious.
• I finally moved the DateUtil class from the sis.studentinfo package to
the sis.util package. This change impacts a number of existing
classes.
• You must also edit Sis and CoursesPanel (and tests) to remove con-
stants and code for constructing filters/formatters. See http://www
.LangrSoft.com/agileJava/code/ for full code.
TABLES 635
• The Field class, which is omitted from these listings, is a simple data
class with virtually no logic.
• You’ll want to update the Course class to contain the new attribute, ef-
fective date.
Tables
The CoursesPanel JList shows a single string for each Course object. This
presentation is adequate because you only display two pieces of data: the de-
partment and course number. Adding the new effective date attribute to the
summary string, however, would quickly make the list look jumbled. The
JList control, in fact, works best when you have only a single piece of data to
represent per row in the list.
A JTable is a very effective control that allows you to present each object
as a sequence of columns. A JTable can look a lot like a spreadsheet. In fact,
JTable code and documentation uses the same terms as spreadsheets: rows,
columns, and cells.
The JTable class gives you a considerable amount of control over look and
feel. For example, you can decide whether or not you want to allow users to
edit individual cells in the table, you can allow the user to rearrange the
columns, and you can control the width of each column.
For this exercise, you’ll replace the JList with a JTable. The best place to
start is to create the data model that will underly the JTable. Just as you in-
serted Course objects into a list model for the JList, you will put Course ob-
jects into a model that you attach to the JTable.
Creating the JTable model is slightly more involved. The JList was able to
Tables
get a printable representation by sending the toString message to each object it
contained. The JTable must treat each attribute for a given object separately.
For every cell it must display, the JTable sends the message getValueAt to the
model. It passes the row and column representing the current cell. The
getValueAt method must return a string to display at this location.
There would be no easy way for the model to figure out what attribute
you want to display for a given column. As such, you must provide a model
implementation yourself. You must supply the getValueAt method in this imple-
mentation, as well as two other methods: getRowCount and getColumnCount. To en-
hance the look and feel of the table, you will likely implement other methods.
The test below, CoursesTableModelTest, shows that the model implements
the method getColumnName to return a text header for each column.
636 SWING, PART 2
package sis.ui;
import junit.framework.*;
import sis.studentinfo.*;
import sis.util.*;
import java.util.*;
Field department =
catalog.get(FieldCatalog.DEPARTMENT_FIELD_NAME);
assertEquals(department.getShortName(), model.getColumnName(0));
Field effectiveDate =
catalog.get(FieldCatalog.EFFECTIVE_DATE_FIELD_NAME);
assertEquals(effectiveDate.getShortName(),
model.getColumnName(2));
}
course.setEffectiveDate(DateUtil.createDate(2006, 3, 17));
Tables model.add(course);
assertEquals(1, model.getRowCount());
final int row = 0;
assertEquals("CMSC", model.getValueAt(row, 0));
assertEquals("110", model.getValueAt(row, 1));
assertEquals("03/17/06", model.getValueAt(row, 2));
}
}
package sis.ui;
import javax.swing.table.*;
import java.text.*;
import java.util.*;
import sis.studentinfo.*;
else if (fieldName.equals(FieldCatalog.EFFECTIVE_DATE_FIELD_NAME))
return formatter.format(course.getEffectiveDate());
return "";
}
Note the subtle redundancies that abound. The table must contain a list of
fields you are interested in displaying. In getValueAt, you obtain the field name
at the column index provided. You use this name in a pseudo–switch state-
ment to determine which Course getter method to call. A shorter bit of code
would involve a switch statement:
switch (column) {
case 0: return course.getDepartment();
case 1: return course.getNumber();
case 2: return formatter.format(course.getEffectiveDate());
default: return "";
}
Domain Maps
The implementation of getValueAt contains significant redundancy. You test the se-
lected field name against each possible field name in order to obtain the corre-
sponding attribute from the Course object. A more radical solution is to
implement your domain objects as hash tables. The hash table key is the attribute
name. You would set the value in the Course like this:
course.set(DEPARTMENT_FIELD_NAME, “CMSC”);
or
String courseName = course.getString(DEPARTMENT_FIELD_NAME);
This solution can greatly diminish the redundancy in your system, but it introduces
other concerns. First is the decreased readability when you attempt to follow the
code or debug it. Also, the solution would require casting, either at the point of call
or embedded within a bunch of utility methods such as getString, getDate, and getInt.
In CoursesPanel:
You can remove the class CourseDisplayAdapter and any references to the
old courses list.
Make sure you take a look at the Java API documentation for JTable and
the various table model classes. The JTable class contains quite a bit of cus-
tomization capabilities.
Feedback
Part of creating a good user experience is ensuring that you provide plenty of
feedback. Sun has already built a lot of feedback into Swing. For example,
when you click the mouse button atop a JButton, the JButton repaints to ap-
pear as if it were physically depressed. This kind of information reassures the
user about his or her actions.
The Sis application lacks a pertinent piece of feedback: When the user en-
ters one of the filtered or formatted text fields, how does he or she know
what they’re expected to type? The user will eventually discover the con-
straints they are under. But he or she will waste some time as they undergo
guesswork, trial, and error.
You can instead provide your users with helpful information ahead of
time. Several options exist:
• Put helpful information in the label for the field. Generally, though,
there is not enough screen real estate to do so.
• Provide a separate online help window that describes how the applica-
tion works.
• As the user moves the mouse over fields, display a small pop-up rectan-
Feedback gle with relevant information. This is known as hover help, or tool tips.
All modern applications such as Internet Explorer provide tool tips as
you hover your mouse over toolbar buttons.
• As the user moves the mouse over fields, display relevant information in
a status bar at the bottom of the window.
For this exercise, you will choose the last option and create a status bar.
Unfortunately, for mouse-based testing, you must usually render (display)
the user interface in order to test it. The reason is that components do not
have established sizes until they are rendered. You can again use the Swing
robot to help you write your tests. Note that the setUp in this test uses a cou-
ple of Swing utility methods that I will display in subsequent listings.
FEEDBACK 641
package sis.ui;
import junit.framework.*;
import javax.swing.*;
import java.awt.*;
import sis.util.*;
statusBar.setInfo(field1, text1);
statusBar.setInfo(field2, text2);
StatusBar
A StatusBar is a JLabel with additional functionality. You can associate an in-
formation String with each text field by sending setInfo to a Status object.
The test extracts the location of the first field, then moves the mouse to an
arbitrary position outside this field. The status bar should show nothing; the
first assertion proves that. The test then moves the mouse over the field, then
ensures that the status bar contains the expected text. The test finally asserts
that the status bar text changes to TEXT2 when the mouse is over the second field.
The SwingUtil class extracts common code used to create a simple panel
and a frame for testing:
package sis.util;
import javax.swing.*;
Feedback In StatusBar, the job of setInfo is to add a mouse listener to the text field.
The listener reacts to mouse entry and mouse exit events. When a user moves
the mouse atop the text, listener code displays the associated information.
When a user moves the mouse away from the text field, listener code clears
the status bar.
package sis.ui;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
public StatusBar() {
super(EMPTY);
setBorder(BorderFactory.createLoweredBevelBorder());
}
The test for StatusBar passes. Now you must attach the status bar to
CoursesPanel. How will you test this? Your test for CoursesPanel could use
the robot again. But it would be easier to ensure that each text field has been
attached to the status bar.
Update the test in CoursesPanelTest. The assertion declares a new intent:
A StatusBar object should be able to return the informational text for a given
text field. It also suggests that the informational text should come from the
field spec object, obtained from the field catalog.
private void assertFields(String[] fieldNames) {
StatusBar statusBar =
(StatusBar)Util.getComponent(panel, StatusBar.NAME);
assertEquals(fieldSpec.getInfo(), statusBar.getInfo(field));
assertLabelText(fieldSpec.getLabelName(), fieldSpec.getLabel());
}
}
This will not compile because you have not implemented getInfo. Note also
that you will need to associate a component name with the status bar. Here
are the changes to StatusBarTest and StatusBar:
// StatusBarTest
...
public void testInfo() {
statusBar.setInfo(field1, "a”);
644 SWING, PART 2
assertEquals("a”, statusBar.getInfo(field1));
}
...
// StatusBar
package sis.ui;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
public StatusBar() {
super(EMPTY);
setName(NAME);
setBorder(BorderFactory.createLoweredBevelBorder());
}
You must also add a field, getter, and setter to Field. You’ll need to modify
FieldCatalog to populate each field with a pertinent informational string:
...
static final String DEPARTMENT_FIELD_INFO =
"Enter a 4-character department designation.";
static final String NUMBER_FIELD_INFO =
"The department number should be 3 digits.";
static final String EFFECTIVE_DATE_FIELD_INFO =
"Effective date should be in mm/dd/yy format.";
...
FEEDBACK 645
put(fieldSpec);
put(fieldSpec);
put(fieldSpec);
}
Finally, the code in CoursesPanel to make it all work for the student infor-
mation system:
JPanel createBottomPanel() {
JLabel statusBar = new JLabel(" ");
statusBar.setBorder(BorderFactory.createLoweredBevelBorder()); Feedback
status = new Status(statusBar);
JPanel createFieldsPanel() {
GridBagLayout layout = new GridBagLayout();
return panel;
}
Feedback
Responsiveness
In the Sis method addCourse, insert a deliberate wait of three seconds. This wait
might emulate the time required to verify and insert the Course object into
the database.
private void addCourse() {
Course course =
new Course(
panel.getText(FieldCatalog.DEPARTMENT_FIELD_NAME),
panel.getText(FieldCatalog.NUMBER_FIELD_NAME));
try { Thread.sleep(3000); } catch (InterruptedException e) {}
JFormattedTextField effectiveDateField =
(JFormattedTextField)panel.getField(
FieldCatalog.EFFECTIVE_DATE_FIELD_NAME);
Date date = (Date)effectiveDateField.getValue();
course.setEffectiveDate(date);
panel.addCourse(course);
}
Execute the application. Enter a department and course number and press
Add. You should experience a 3-second delay. During that time, you cannot
do anything else with the user interface! For an end user, this is a frustrating
experience, since there is no feedback about why the application is not
responding.
As an application developer, you can do two things with respect to respon-
siveness: First, provide feedback to the user that they should wait patiently
for a short period. Second, ensure that the user is able to do other things
while waiting.
Feedback comes in the form of a “wait” cursor. Windows represents a
wait cursor using an hourglass. Some Unix desktops represent a wait cursor
Responsiveness
using a clock. You should provide an hourglass for any operation that does
not immediately return control to the user.
private void addCourse() {
frame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
Course course =
new Course(
panel.getText(FieldCatalog.DEPARTMENT_FIELD_NAME),
panel.getText(FieldCatalog.NUMBER_FIELD_NAME));
try { Thread.sleep(3000); } catch (InterruptedException e) {}
JFormattedTextField effectiveDateField =
(JFormattedTextField)panel.getField(
FieldCatalog.EFFECTIVE_DATE_FIELD_NAME);
648 SWING, PART 2
panel.addCourse(course);
}
finally {
frame.setCursor(Cursor.getDefaultCursor());
}
}
A subtle but real problem exists with this code. It is not thread-safe! Since
Swing uses a separate thread known as an event dispatch thread, it is possible
RESPONSIVENESS 649
for a user to click the Add button before the panel is completely updated.
The user might see unexpected results.
You can rectify this by executing statements to update the user interface in
the event dispatch thread. The class javax.swing.SwingUtilities contains two
methods, invokeLater and invokeAndWait, to allow this. Each method takes a
Runnable object that defines the code to execute in the event dispatch thread.
You use invokeLater when you can allow the run method to execute asynchro-
nously (when you don’t need to “hold up” activities on the user interface). In
our example, you want to use invokeAndWait, which results in the run method
being executed synchronously.
Here’s how addCourse might look using invokeAndWait:
The big downside is that this change will break the SisTest method testAdd-
Course.
The test presumes that clicking on Add will block until the course has
been added to the panel. As a quick fix, you can have the test wait until ele- Responsiveness
ments appear in the panel’s table.
while (panel.getCourseCount() == 0)
;
Course course = panel.getCourse(0);
assertEquals("MATH", course.getDepartment());
assertEquals("300", course.getNumber());
TestUtil.assertDateEquals(2006, 3, 17, course.getEffectiveDate());
}
650 SWING, PART 2
int getCourseCount() {
return coursesTableModel.getRowCount();
}
Remaining Tasks
You’ve invested a considerable amount of code in this simple interface. Yet
it’s far from complete. Here is a list of some of the things you might consider
coding to complete the interface:
I’ve no doubt left a few features off this list. Building a robust, sophisti-
cated user interface is a lot of work. The absence of any of these features will
severely cripple the effectiveness of your application. However, rather than
you as a developer trying to figure out what you need, you should treat each
of these features as customer requirements. Your customer team needs a
qualified expert to design and detail the specifications for the user interface.6
Final Notes
A large number of Swing books exist. Many are very thick, suggesting that
there is quite a bit to learn about Swing. Indeed, there is. In these short two
chapters you have only scratched the surface of Swing.
However, you have seen the basics of how to construct Swing applica-
tions. You would be able to build a decent interface with this small bit of in-
formation. No doubt you will want to learn about more complex Swing
topics, such as tree widgets and drag & drop. A bit of searching should pro-
vide you with what you need to know. As always, the Java API documenta-
tion is the best place to start. Often the API documentation will lead you to a
Sun tutorial on the topic.
I hope you’ve learned the more important lesson in these two Swing chap-
ters: how to approach building a Swing application using TDD. Unit-testing
Swing is often viewed as too difficult, and many shops choose to forgo it al- Final Notes
together. Don’t succumb to the laziness: The frequent result of not testing
Swing code is the tendency for Swing classes to bloat with easily testable ap-
plication or model code.
Looking at the resultant view class, CoursesPanel, you should have no-
ticed that it is very small and simple. Other than layout, there is little com-
plexity in the class; it does almost nothing. TDD or not, that is always the
6
This person can be a developer acting in the role of UI expert for the customer team.
Do not, however, underestimate the importance of this role. Most developers, even
those who have read a book or two on the topic, don’t have a clue how to create an
effective user experience.
652 SWING, PART 2
• Ensure that your design separates application, view, and domain logic.
• Break down design even further by considering the Single-Responsibil-
ity Principle.
• Eliminate the otherwise excessive redundancies in Swing (e.g., use com-
mon methods to create and extract fields).
Final Notes
• Use listeners to test abstract reactions of the view (e.g., to ensure that
clicking a button results in some action being triggered).
• Use mock classes to avoid Swing rendering.
• Don’t test layout.
• Use the Swing robot, but only as a last resort.
Swing was not designed with unit-level testing in mind. Figuring out how
to test Swing is a problem-solving exercise. Dig through the various Swing
classes to find what you need. Sometimes they’ll supply the hooks to help
you test; other times they won’t. You may need to think outside the box to
figure out how to test things!
Additional Lesson III
Java Miscellany
This chapter provides a handful of Java odds and ends. The primary goal of
this chapter is twofold:
• It will present you with an overview of some very involved APIs that
can take entire books to cover adequately.
• It will discuss a few core Java odds and ends that didn’t fit elsewhere in
Agile Java.
You will learn about:
• JARs
• the finalize method
• regular expressions
• JDBC
• internationalization
• call by reference versus call by value
• Java periphery: quick overviews of various other Java APIs, with point-
ers to more information
JARs
JARs
When you learned about the Java classpath in Lesson 1, you learned to add
class folders (or directories) to your classpath. A class folder contains discrete
class files that Java looks for when it needs to load a class.
Your classpath can include JARs in addition to, or instead of, class folders.
A JAR (Java ARchive) is a single file that collects Java class files and other
653
654 JAVA MISCELLANY
resources needed at runtime. You can create JARs using the jar command-line
utility executable supplied in your Java bin directory. You can also create
JARs using Ant.
An understanding of JARs is absolutely essential to being able to work in
any production Java environment. The usage of JARs falls under the category
of deployment, so the discussion of JARs didn’t have an appropriate place
earlier in the book.
The jar utility combines class files and other resources (such as images or
property files) into a single file with an extension of .jar. The utility creates
JARs as ZIP files, a file format you are likely familiar with. A ZIP file uses a
standard compression algorithm to reduce the overall space requirements.
There are many reasons to use JARs:
JARs are the basis for deploying classes to a web server or enterprise Java
installation.
To create a JAR for the SIS application, first navigate to the classes direc-
tory. Then execute:
In the above jar command, the options cvf (c, v, and f) tell the JAR com-
mand to create a new JAR, to provide verbose output (not necessary but use-
ful), and to use a filename of sis.jar. The * at the end of the command tells the
jar program to archive all files in the current directory and all subdirectories.
You can execute the command jar with no arguments to display a brief sum-
mary of the command and its proper usage.
JARS 655
After executing the jar cvf command, you should have a file named sis.jar.
Under Windows, you can open this JAR using WinZip. Under any platform,
you can use the jar command to list the contents of the JAR.
jar tvf sis.jar
The only difference in the command is the option t (list table of contents)
instead of c. You should see output similar to this:
0 Mon Aug 02 23:25:36 MDT 2004 META-INF/
74 Mon Aug 02 23:25:36 MDT 2004 META-INF/MANIFEST.MF
553 Sat Jul 24 10:41:28 MDT 2004 A$B.class
541 Sat Jul 24 10:41:28 MDT 2004 A.class
1377 Sat Jul 24 10:41:28 MDT 2004 AtomicLong.class
0 Sat Jul 24 10:41:28 MDT 2004 com/
0 Sat Jul 24 10:41:28 MDT 2004 com/jimbob/
0 Sat Jul 24 10:41:28 MDT 2004 com/jimbob/ach/
486 Sat Jul 24 10:41:28 MDT 2004 com/jimbob/ach/Ach.class
377 Sat Jul 24 10:41:28 MDT 2004 com/jimbob/ach/AchCredentials.class
516 Sat Jul 24 10:41:28 MDT 2004 com/jimbob/ach/AchResponse.class
1164 Sat Jul 24 10:41:28 MDT 2004 com/jimbob/ach/AchStatus.class
...
You can extract files from the JAR using the command option x (i.e.,
jar xvf).
Once you have added classes to a JAR, you can add the JAR to your class-
path. Java will dig into the JAR file to look for classes it needs to load. For
example, suppose you deploy sis.jar to the directory /usr/sis. You can execute
the SIS application using the command:
java -cp /usr/sis/sis.jar sis.ui.Sis
In order for Java to find a class within a JAR, its path information must
match the package information. To locate the class com.jimbob.ach.Ach,
Java must be able to find an entry in the JAR with the complete pathname
com/jimbob/ach/Ach.class. In the jar tvf listing, I’ve shown this entry in bold.
If you were a directory higher when you were creating the JAR, the path
information in the JAR file might be:
JARs
486 Sat Jul 24 10:41:28 MDT 2004 sis/com/jimbob/ach/Ach.class
Java would not match up this entry with the class com.jimbob.ach.Ach.
The jar utility adds a manifest file, META-INF/MANIFEST.MF, to the ZIP file. The
manifest file contains information about the contents of the JAR (i.e., meta-
information).
One use for the manifest file is to indicate a “main” class. If you specify a
main class, you can initiate an application by providing only the JAR name.
656 JAVA MISCELLANY
You can accomplish this by first creating a separate manifest file with the
contents:
Main-Class: sis.ui.Sis
Make sure you include a blank line as the last line of this file! Otherwise
Java may not recognize the manifest entry.
When you create the JAR, specify the manifest file using the command:
jar cvmf main.mf sis.jar *
The m option stands for manifest, of course. If you look at the file MANIFEST.MF
in sis.jar, it should look something like this:
Manifest-Version: 1.0
Created-By: 1.5.0 (Sun Microsystems Inc.)
Main-Class: sis.ui.Sis
You can now initiate the SIS application using the simplified command:
java -jar sis.jar
You can manipulate JARs (and ZIP files) programmatically. Java supplies
a complete API in the package java.util.zip to help you accomplish this. The
java.util.zip package contains tools to both read and write JARs. This allows
you to write unit tests against JAR operations about as simply as if you were
testing against text files.
Regular Expressions
A regular expression, also known as a regex or regexp, is a string containing
a search pattern. The regular expression language is a fairly standardized lan-
guage for pattern-matching specifications. Other languages such as Perl and
Ruby provide direct support for regular expressions. Programmers’ editors
such as TextPad and UltraEdit allow you to search file text using regular ex-
Regular pressions. Java supplies a set of class libraries to allow you to take advantage
Expressions of regular expressions.
You might have used a wildcard character ('*') to list files in a directory.
Using the wildcard tells the ls or dir command to find all matching files, as-
suming that the * can expand into any character or sequence of characters.
For example, the DOS command dir *.java lists every file with a .java extension.
The regular expression language is similar in concept but far more power-
ful. In this section, I’ll introduce you to regular expressions using a few sim-
ple examples. I’ll show you how to take advantage of regular expressions in
REGULAR EXPRESSIONS 657
your Java code. I’ll suggest how you might approach testing Java code that
uses regular expressions. But a complete discussion of regular expressions is
way out of the scope of this book. Refer to the end of this section for a few
sites that provide regular expression tutorials and information.
Splitting Strings
Back in Lesson 7, you used the String method split to break a full name into
discrete name parts. You passed a String containing a single blank character
to split:
The split method takes a regular expression as its sole argument.1 The split
method breaks the receiving string up when it encounters a match for the
regular expression.
Suppose you try to split the string "Jeffrey Hyman"2 with three spaces separat-
ing the first and last name. You want the results to be the strings "Jeffrey" and
"Hyman", but a passing language test represents the actual results:
1
An overloaded version of split takes a limit on the number of times the pattern is
applied.
2
Aka Joey Ramone. Gabba gabba hey.
658 JAVA MISCELLANY
Combining the two ideas, the regular expression string \s+ will match on
sequences with one or more whitespace characters.
I’ve moved the name-splitting code from the Student class and into a class
called sis.studentinfo.Name. You can find the complete code for NameTest
and Name at http://www.LangrSoft.com/agileJava/code. The listings below
of NameTest and Name show only relevant or new material.
// NameTest.java
public void testExtraneousSpaces() {
final String fullName = "Jeffrey Hyman";
Name name = createName(fullName);
assertEquals("Jeffrey", name.getFirstName());
assertEquals("Hyman", name.getLastName());
}
// Name.java
private List<String> split(String fullName) {
List<String> results = new ArrayList<String>();
for (String name: fullName.split(" "))
results.add(name);
return results;
}
The test method testExtraneousSpaces will not pass given the current imple-
mentation in Name.split. Using the new pattern you learned about, you can up-
date the split method to make the tests pass:
private List<String> split(String fullName) {
List<String> results = new ArrayList<String>();
for (String name: fullName.split("\\s+"))
results.add(name);
return results;
}
Regular The backslash character (‘\’) represents the start of an escape sequence in
Expressions Java strings. Thus, the regular expression \s+ appears as \\s+ within the con-
text of a String.
Since \s matches any whitespace character, change the test to demonstrate
that additional whitespace characters are ignored.
public void testExtraneousWhitespace() {
final String fullName = "Jeffrey \t\t\n \r\fHyman";
Name name = createName(fullName);
assertEquals("Jeffrey", name.getFirstName());
assertEquals("Hyman", name.getLastName());
}
REGULAR EXPRESSIONS 659
import java.util.regex.*;
String testMethodRegex =
"public\\s+void\\s+test\\w*\\s*\\(\\s*\\)\\s*\\{";
Pattern pattern = Pattern.compile(testMethodRegex);
Matcher matcher = pattern.matcher(text);
assertTrue(matcher.find());
assertEquals(text.indexOf(textLines[1]), matcher.start());
assertTrue(matcher.find());
assertEquals(text.indexOf(textLines[3]), matcher.start());
assertFalse(matcher.find());
}
The regular expression in testMethodRegex looks pretty tough. They can get
much worse! The regular expression above doesn’t handle the possibility of a
static or abstract modifier.3 The double backslashes in the regular expression
don’t help matters.
Breaking the pattern down into its constructs makes it pretty straightfor-
ward. I’ll step through each construct, left to right in the expression:
public\\s+void\\s+test\\w*\\s*\\(\\s*\\)\\s*\\{
3
Perhaps regex isn’t the best tool for this job.
REGULAR EXPRESSIONS 661
regular expression. You can also send lookingAt, which returns true if the start
of the input string (or the entire string) matches the regular expression.
4
[Bloch2001].
CLONING AND COVARIANCE 663
Even though clone is defined on the class Object, the test won’t compile.
Object defines the clone method as protected. You must override the clone
method in the Course subclass and make it public. The clone method is defined
in Object as:
protected native Object clone() throws CloneNotSupportedException;
The native keyword in the signature indicates that the method is not imple-
mented in Java but instead in the JVM.
To be able to clone an object, its class must implement the Cloneable inter-
face. Cloneable is a marker interface—it doesn’t define clone, as you might ex-
pect. Instead, its goal is to prevent clients from cloning a class that was not
explicitly designed to be cloneable.
The implementation in Course:
package sis.studentinfo;
...
public class Course implements java.io.Serializable, Cloneable {
private String department;
private String number;
private Date effectiveDate;
...
@Override
public Course clone() {
Course copy = null;
try {
copy = (Course)super.clone();
}
catch (CloneNotSupportedException impossible) {
throw new RuntimeException("unable to clone");
}
return copy;
}
}
The first thing you might notice is that the clone method returns an object
of the type Course—not Object, as defined in the superclass. This is a capa-
bility of Java known as covariance. Covariance is the ability of a subclass
method to return an object that is a subclass of the superclass return type. Cloning and
Cloning is the canonical example for covariance in Java. Covariance
The core of the clone method is the call to the superclass clone method. You
always want to call the superclass clone method. The clone method as imple-
mented in Object (in the VM actually) performs a bitwise copy of the con-
tents of the object. This means that it copies values for every field defined on
the class. References are copied as well, but the objects they refer to are not
copied. For example, for the effectiveDate field in Course, both the original and
the clone refer to the same Date object.
664 JAVA MISCELLANY
The default clone behavior, in which references are copied but not the ob-
jects to which they point, is known as a shallow clone. If you want a deep
clone—a copy where the contained objects are copied also—you must imple-
ment the details yourself in the clone method.
The call to super.clone can throw a CloneNotSupportedException. This is
what requires your class to implement the Cloneable marker interface.
JDBC
The vast majority of business-oriented applications access relational data-
bases such as Oracle, MySQL, Sybase, SQL Server, and DB2. Applications re-
quire information to be available and dependable from execution to
execution—to be persistent. A relational database5 provides an application-
independent means of storing and accessing that information.
A relational database is comprised of tables. A table is a matrix of rows
and columns. A column is an attribute. A row is a collection of column val-
ues. For example, a student table might have columns ID, name, and city. A
row exists in the database for each student. A representation of a student
table appears in Table 1. In the table, there are two rows and thus two stu-
dents: Schmoo, living in the city of Pueblo, and Caboose, living in the city of
Laurel.
The language SQL (Structured Query Language) allows you to store and
retrieve information from any relational database. SQL, for the most part, is
standard across database implementations. An SQL statement to access stu-
dent records from an Oracle database will work for accessing student records
from Sybase (with perhaps minor modifications). In Java, you use an API
called JDBC (Java DataBase Connectivity) to interact with the database.
5
You may hear the term DBMS—database management system—to refer to the soft-
ware that manages the database. An RDBMS is a relational DBMS.
JDBC 665
JDBC allows you to establish connections to the database and execute SQL
against it.
The need for database interaction is so commonplace that many Java
products exist to simplify it. Enterprise Java Beans (EJBs), Hibernate, and
JDO are three of the more popular attempts to simplify Java persistence.
JDBC is the foundation of many of these tools. As is often the case, you are
better off learning how the foundation works before considering use of a
higher-level tool. Doing so will give you a better understanding of what the
tools are doing. You may even find that a custom, highly refactored JDBC
implementation is the better solution.
In this brief section, I’ll demonstrate how to write simple JDBC tests and
code to interact with a database. Just like Swing, entire books and many
good web sites on JDBC exist. You will want to consult additional resources
for more details on JDBC. You will also want to consult additional resources
on SQL. I will provide only minimal explanation about the SQL in this
section.
The examples in this section are written to interact with MySQL, a database
freely available at http://www.mysql.com. Most of the examples are applicable
to any other database. However, some of the code details for connecting to the
database will differ. Also, a few of the “getting started” tests make presump-
tions about the availability of test structures within the database.
You will also need a JDBC driver for MySQL (see http://dev
.mysql.com/downloads/connector/j/3.0.html). A driver is a Java library that
meets Sun’s JDBC interface specification. Your code always interacts with the
same Sun-supplied JDBC interface methods, regardless of the database or
database driver vendor. The database driver implementation adapts JDBC
API calls to the specific requirements of the database itself.
Connecting to a Database
Perhaps the most difficult aspect of interacting with databases is establishing
a connection to them. Installing MySQL always creates a database named
test that you can access. Your first test will ensure that you can access the JDBC
database test.
Make sure you have installed the MySQL JDBC driver and added it to
your classpath. Note that you won’t need the driver on the classpath to com-
pile, but you will need it to execute.
package sis.db;
import junit.framework.TestCase;
import java.sql.*;
666 JAVA MISCELLANY
The test is simple enough. Create a JdbcAccess instance using the database
name test. Request a connection from the access object and verify that the
connection is open (not closed). Make sure the connection gets closed by
using a finally block.
A couple of comments: First, the test itself throws SQLException. You’ll
want to quickly encapsulate SQLException—you want as few classes as pos-
sible to even know that you are using JDBC.
Second, you will not usually want client code to request its own connec-
tion object. A common problem in many systems is that the client code for-
gets to close the connection. Clients open more and more connections.
Ultimately, the system crashes when it becomes unable to supply any more
connections. For now, writing a test to prove the ability to establish a con-
nection allows you to proceed incrementally. You probably won’t want to
publicize getConnection.
package sis.db;
import java.sql.*;
The original reason for suggesting the use of reflection is flexibility. Using
Class.forName,you could load the driver class name from a property file or pass
it in via a system property (see Properties in this chapter). You would then
have the flexibility to change the driver at a later time without changing
code. In reality, changing drivers doesn’t happen that frequently, and you’ll
probably have to revisit your code anyway if you do.7
When you call Class.forName (or when you first reference and instantiate the
class), the static initializer in the Driver class is invoked. Code in the static
initializer is responsible for registering the driver with the DriverManager.
The DriverManager maintains a list of all registered drivers and selects a suit-
able one based on the URL when getConnection gets called.
Another means of specifying one or more drivers is to supply class names
using the system property jdbc.drivers. Refer to the Properties section later in
this chapter for information on how to set this property.
The organization of information in the database URL is specific to the dri-
ver vendor. It usually follows a few conventions, such as starting with the
word jdbc followed by a subprotocol string (mysql in this case; it’s often the
JDBC
vendor name). You separate elements in the URL using colons (:). In
MySQL, you follow the subprotocol with a reference to the server (localhost
here—your machine) and the database name.
6
MySQL does not. Check your driver documentation. Or you can be lazy and always
call newInstance.
7
Of course, the need to support a second driver is reason enough to eliminate the
hard-coded class reference.
668 JAVA MISCELLANY
Executing Queries
The second test demonstrates executing SQL statements.
package sis.db;
import junit.framework.TestCase;
import java.sql.*;
As a final measure, the test removes the testExecute table by using the SQL
command drop table.
A slightly refactored JdbcAccess shows the new methods execute and get-
FirstRowFirstColumn. The refactoring eliminates some code duplication. It also
eliminates processing duplication by ensuring that the driver instance only
gets loaded once. Both the execute and getFirstRowFirstColumn methods obtain and
relinquish their own connection. This alleviates the client from that responsi-
bility.
package sis.db;
import java.sql.*;
try {
Statement statement = connection.createStatement();
ResultSet results = statement.executeQuery(query);
results.next();
return results.getString(1);
}
finally {
close(connection);
}
}
}
You need a Statement object in order to execute SQL. You obtain a State-
ment object from a Connection by sending it createStatement. Once you have a
Statement, you can either use the execute method to invoke SQL statements
where you expect no results to come back or executeQuery to invoke SQL state-
ments and subsequently obtain results. To execute or executeQuery, you pass the
SQL string as an argument.
Results return in the form of a ResultSet object. A ResultSet is very similar
to an Iterator. It maintains an internal pointer to the current row. You ad-
vance this pointer, which initially points to nothing, by sending the message
next to the ResultSet. The method next returns true until the internal pointer
moves past the last row. You access columns from the current row by either
column name or column index. Various methods exist for obtaining column
values, each differing by the type of data stored in each column. The Result-
Set method getString returns character data, for example, and the method
getInt returns an int value.
The code in getFirstRowFirstColumn advances the pointer to the first row re-
turned from executeQuery by calling results.next(). The getFirstRowFirstColumn method
returns the value of the first column by sending getString to the ResultSet using
its index, 1, as an argument. Column indexes start at 1 in JDBC, not 0.
Prepared Statements
JDBC Often you will want to execute the same statement numerous times in an ap-
plication, different only perhaps by key information. For example, the SIS
application requires rapid student lookups based on student identification
(ID) number.
Since you pass an SQL statement in the form of a String, the statement
must be compiled before the database can execute it. Compiling the state-
ment ensures that it follows valid SQL syntax. Compilation takes enough ex-
ecution time that it can cause performance problems. (Profile, as always, to
be sure that you have poor performance.) To speed things up, you can create
JDBC 671
For each student lookup, you bind a value to the question mark by using
set methods. The method statement.setString(1, "boo") would bind the value ‘boo’
to the first (and only) question-mark argument.
public void testQueryBy() throws SQLException {
drop("testQueryBy");
access.execute(
"create table testQueryBy (id varchar(10), name varchar(30))");
PreparedStatement statement = null;
try {
access.execute("insert into testQueryBy values(‘123’, ‘schmoe’)");
access.execute(
"insert into testQueryBy values(‘234’, ‘patella’)");
statement =
access.prepare("select id, name from testQueryBy where id = ?");
paredStatement. In this case, you will have to trust that client code closes out
the connection when it is done using the PreparedStatement.
The implementation:
with the JDBC API. Most of the remainder of the application is blissfully ig-
norant of the fact that JDBC is the persistence mechanism. If you choose to
supplant JDBC with an object-oriented database down the road, replacement
work is minimal.
You can create mapping objects to translate between database columns
and domain attributes. You can accomplish this using code generation
(which is preferable) or reflection. It is even possible to store everything in
String format in the database and do type translation a layer up from
JdbcAccess. This can help keep JdbcAccess simple: It provides access meth-
ods that return generic lists of lists of strings (rows of columns, each of which
are strings).
SQL statements contain rampant duplication. It is fairly easy to code a
SQL generator class that builds SQL statements using information available
from the database (the database metadata).
Internationalization
A significant amount of software product gets deployed to all corners of the
globe. No longer can you get away with developing an application for only
your home language. Some localities, such as Quebec, require all software to
execute in two or more languages. Success in the global marketplace hinges
on the ability to sell your software in many countries using many different
languages.
Building software so that it supports different languages and cultures is
known as internationalization, or i18n8 in shorthand. Preparing software for
delivery to a single locale is known as localization. A locale is a region. Geog-
raphy, culture, or politics can define locales.
Retrofitting a large, existing application to support multiple languages can
be expensive. This might lead you to believe that you must internationalize
your application from the outset of development. Perhaps. Extreme adher-
ence to the “no duplication” rule can leave you with a design that supports a Internationalization
rapid transition to internationalized software. Nonetheless, building a foun-
dation for internationalization can make things easier. You can justify these
building blocks because they do help eliminate duplication.
Use internationalization mechanisms to help eliminate duplication
and position you to localize your application.
8
Count the number of letters between the first and last letters of internationalization.
674 JAVA MISCELLANY
Resource Bundles
String literals embedded directly within code cause problems. Most significant,
they create duplication. If a literal appears in production code and if you’ve
been testing everything like you should, some test will duplicate that same lit-
eral. Also, the meaning of the literal is not always clear. In Agile Java, I had
you extract String literals to class constants. Both the test and production code
can refer to the same constant. The name of the constant imparts meaning.
Still, having to maintain a bunch of class constants is painful. Consider
also the needs of the international application. The ideal solution involves ex-
tracting all literals to a common file. When you must deploy the software to
a different locale, you provide a new file that contains the translated text.
Instances of the Java class java.util.ResourceBundle manage interaction
with locale-specific resource files. To internationalize your software, you’ll
update all code to ask a ResourceBundle for a localized message String.
Using a ResourceBundle is pretty easy. You obtain a ResourceBundle by
calling the creation method getBundle, passing it the name of the bundle. You
then work with the ResourceBundle like a map—you pass it a key, it gives
you a localized object in return.
Testing use of a ResourceBundle is another matter. You want to ensure
that you can read a given property and its value from a ResourceBundle. But
you can’t presume that a key and certain value already exists in the resource
file. The easiest solution would be to write out a small file with a known key
and value. The problem is, you don’t want to overwrite the resource file that
the rest of your application requires.
package sis.util;
import junit.framework.TestCase;
import java.io.IOException;
TestUtil.delete(FILENAME);
}
The test carefully manages a test resource file using setUp and tearDown. It in-
teracts with a class you will create named sis.util.Bundle to retrieve the name
of the existing bundle. The test stores the existing resource file base name,
then tells Bundle to use a new base name. Only the test will recognize this
base name. The test itself (testMessage) writes to the test resource file. Each
entry in the resource file is a key-value pair separated by an equals sign (=).
The assertion in testMessage calls the get method on the Bundle class to re-
trieve a localized resource. Finally, the tearDown method resets Bundle to use the
original base name.
package sis.util;
import java.util.ResourceBundle;
Localization
Suppose you must deploy your application to Mexico. You would send your
resource file to a translator for localization. The translator’s job is to return a
new file of key-value pairs, replacing the base value (perhaps English) with a
Spanish translation.
(In reality, the translator will probably need to interact with you or some-
one else who understands the application. The translator will often require
contextual information because of multiple word meanings. For example,
your translator may need to know whether “File” is a verb or a noun.)
You must name the file that the translator returns to you according to the
specific Locale. A locale is a language, a country (which is optional), and a
variant (also optional; it is rarely used). For deployment to Mexico, the lan-
guage is Spanish, indicated by "es" (español). The country is "MX" (Mexico). To
come up with a resource file name, you append these bits of locale informa-
tion to the base name, using underscores ('_') as separators. The Mexican-
localized file name for the base name "Messages" is "Messages_es_MX.properties".
Internationalization
You can request a complete list of locales supported on your platform
using the Locale class method getAvailableLocales. Code and execute this ugly bit
of code:
The output from executing this code snippet will give you appropriate ex-
tensions to the base name.
Here is BundleTest, modified to include a new test for Mexican localiza-
tion:
package sis.util;
import junit.framework.TestCase;
import java.io.IOException;
import java.util.Locale;
}
}
Formatted Messages
Entries in resource files may contain substitution placeholders. For example:
dependentsMessage=You have {0} dependents, {1}. Is this correct?
INTERNATIONALIZATION 679
The value for dependentsMessage contains two format elements: {0} and {1}.
Code that loads this string via a ResourceBundle would use a MessageFor-
mat object to replace the format elements with appropriate values. The fol-
lowing language test demonstrates:
public void testMessageFormat() {
String message = "You have {0} dependents, {1}. Is this correct?";
Why not just use the java.util.Formatter class? The reason is that the
MessageFormat scheme predates the Formatter class, which Sun introduced
in Java 5.0.
A more interesting reason for using the MessageFormat scheme is to take
advantage of the ChoiceFormat class. This can eliminate the need to code ad-
ditional if/else logic in order to supply format element values. The classic ex-
ample shows how to manage formatting of messages that refer to numbers.
We want to present a friendlier message to Señor Wences, and telling him
that he has “one dependents” is unacceptable.9
Using ChoiceFormat is a bit of a bear. You first create a mapping of nu-
meric ranges to corresponding text. You refer to the ranges, expressed using
an array of doubles, as limits. You express the corresponding text, or for-
mats, in an array of strings.
double[] dependentLimits = { 0, 1, 2 };
String[] dependentFormats =
{ "no dependents", "one dependent", "{0} dependents" };
The numbers in the limits array are ranges. The final element in the limits Internationalization
array represents the low end of the range—two dependents and up, in this
case. For two dependents, the ChoiceFormat would apply the corresponding
format string "{0} dependents”, substituting the actual number of dependents
for {0}.
Using these limits and formats, you create a ChoiceFormat object:
ChoiceFormat formatter =
new ChoiceFormat(dependentLimits, dependentFormats);
9
No doubt he would have responded indignantly, “Tell it to the hand.”
680 JAVA MISCELLANY
A message string might have multiple format elements. Only some of the
format elements might require a ChoiceFormat. You must create an array of
Format objects (Format is the superclass of ChoiceFormat), substituting null
where you have no need for a formatter.
Format[] formats = { formatter, null };
You can then create a MessageFormat object and set the array of formats
into it:
MessageFormat messageFormatter = new MessageFormat(message);
messageFormatter.setFormats(formats);
You’re now able to use the MessageFormat as before. The entire process is
shown in the language test testChoiceFormat.
public void testChoiceFormat() {
String message = "You have {0}, {1}. Is this correct?";
double[] dependentLimits = { 0, 1, 2 };
String[] dependentFormats =
{ "no dependents", "one dependent", "{0} dependents" };
ChoiceFormat formatter =
new ChoiceFormat(dependentLimits, dependentFormats);
assertEquals(
"You have one dependent, Señor Wences. Is this correct?",
messageFormatter.format(new Object[] { 1, "Señor Wences" }));
assertEquals(
"You have 10 dependents, Señor Wences. Is this correct?",
messageFormatter.format(new Object[] { 10, "Señor Wences" }));
}
Internationalization Interaction with ChoiceFormat can be even more complex. Refer to the
Java API documentation for the class.
Note that the format strings should come from your ResourceBundle.
function completes execution. The reason is because changes are not actually
made to the argument but to its local copy. The copy of the argument has
only method scope.
Call by reference means that the function works on the same physical ar-
gument that was passed in. Any changes to the argument are retained.
Java is entirely call by value. If you pass an int to a method, the method
operates on a copy of the int. If you pass an object reference to a method, the
method operates on a copy of the reference—not a copy of the object itself.
I’ll demonstrate both of these statements in code.
Understanding call by value with respect to primitives is easy enough.
Here’s a test to show you how the method’s copy of the primitive gets dis-
carded.
public void testCallByValuePrimitives() {
int count = 1;
increment(count);
assertEquals(1, count);
}
Even though the increment method changes the value of count, the change is
to a local copy. The local copy disappears upon exit of the increment method.
Code in testCallByValuePrimitives remains oblivious to any of these shenanigans.
Remember that a reference is a pointer—an address of an object’s location
in memory. The called method copies this address, creating a new pointer
that refers to the same memory location. If you assign a new memory address
(i.e., a different object) to the reference within the called method, this new
address is discarded when the method completes.
Code in the called method can send messages to the object pointed at by
the reference. These message sends can effect permanent changes in the
object.
In testCallByValueReferences, shown following this paragraph, code creates a cus-
Call by tomer with an ID of 1. The test then calls the method incrementId. The first line
Reference of code in incrementId grabs the existing ID from the customer, adds 1 to this
versus Call
number, and sets the sum back into the customer. The second line in incrementId
by Value
creates a new Customer object and assigns its address to the customer argument
reference. You don’t want to do this!11 The assignment is discarded when incre-
mentId completes. The test finally verifies that the customer ID is 2, not 22.
11
You might consider declaring all your arguments as final. Attempts to assign to the
argument will result in compile failure. My ingrained habits prevent me from doing
this consistently.
JAVA PERIPHERY 683
class Customer {
private int id;
Customer(int id) { this.id = id; }
void setId(int id) { this.id = id; }
public int getId() { return id; }
}
Java Periphery
Java gives you a number of mechanisms to help your application interact
with the local operating environment.
Properties
A small hash table of system properties is always available from any point in
your application. This table contains useful information, including things
about your Java environment and your operating system. You can obtain the
entire set of properties using the java.lang.System method getProperties:
Properties existingProperties = System.getProperties();
12
You can set this property to a new value, but it has no effect on the current Java ex-
ecution. The Java VM does not reread this value. If you think you need to change the
classpath, you may need to build a custom class loader instead.
684 JAVA MISCELLANY
file.separator the file separator ('/' under Unix; '\' under Windows)
path.separator the separator between entries in a path (':' under Unix;
';' under Windows)
assertEquals("1.5.0-beta3", System.getProperty("java.version"));
System properties are global. You can access them from anywhere in your
code.
You can add your own global properties to the list of system properties.
Generally, a database is a better place for this sort of information. Your main
reason to use properties should be to dynamically alter small pieces of infor-
mation from one application execution to the next. For example, your de-
ployed application may need to execute in “kiosk” mode most of the time,
looping and occupying the full screen without borders or a title bar. During
development, you may want to be able to switch rapidly back and forth be-
tween execution modes.
You can accomplish this dynamism in a number of ways. You could put a
mode setting flag into a database, but having to modify data in a database
can slow you down. You could pass the information as command line argu-
ments and manage it in your main method. But you might not need the infor-
mation until later in your application; what do you do with it until then?
You can add your property to the system properties programmatically:
Or, as you saw with logging in Lesson 8, you can set the property as a VM
argument:
If a property is not set, getProperty returns null. You can provide a default
value to getProperty to be returned if the property is not set. A language test
shows how this works:
JAVA PERIPHERY 685
Property Files
One technique you will encounter in many shops and many applications is
the use of property files. A property file is similar to a resource file; it is a col-
lection of key-value pairs in the form key=value. Developers often use property
files as a quick-and-dirty catchall for configurable values. Things such as
class names of drivers, color schemes, and boolean flags end up in property
files.
One driving force is the need to change certain values as software moves
from a production environment into a test environment, an integration envi-
ronment, or a production environment. For example, your code might need
to connect to a web server. The web server name you use for development
will differ from the web server name for production.
I recommend that you minimize your use of property files or at least mini-
mize the entries that go in them. Property file entries are disjointed from your
application; they represent a bunch of global variables arbitrarily clustered
together. You should prefer instead to carefully associate configurable values
with the classes they impact. The better way to do this is through the use of a
database.
Keep any property files small and simple or, better yet, eliminate
them. Resist adding new entries.
If you must use property files, the Properties class contains methods for Java Periphery
loading from an input stream that is either in key-value text format or in XML
format. Do not try to dynamically update this file from your executing applica-
tion. Instead, consider the Preferences API, discussed in the next section.
Preferences
For critical information that you must retain from application execution to
execution, you should use a database or other trustworthy persistence mech-
anism. However, you often want the ability to quickly and simply store
686 JAVA MISCELLANY
various bits of noncritical information. Usually this is in the area of user triv-
ialities: preferred colors, window positioning, recently visited web sites, and
so on. Were any of this information lost, it would be a nuisance to a user but
would not prevent him or her from accurately completing work.
The Java Preferences API supplies a simple local persistence scheme for
this data. PersistentFrameTest (shown after the next paragraph) demon-
strates. The first test, testCreate, shows that the first time a frame is created, it
uses default window positions. The test testMove moves a frame, then closes it.
The test shows that subsequent frames use the last position of any Persistent-
Frame.
In order for PersistentFrameTest to work more than once, the setUp method
sends the message clearPreferences to the frame. This method ensures that the
PersistentFrame object uses default window placement settings.
package sis.ui;
import junit.framework.*;
import java.awt.*;
import java.util.prefs.BackingStoreException;
frame.dispose();
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.prefs.*;
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
addWindowListener(new WindowAdapter() { Java Periphery
public void windowClosed(WindowEvent e) {
saveWindowPosition();
}
});
}
preferences.putInt(X, bounds.x); // 3
preferences.putInt(Y, bounds.y);
688 JAVA MISCELLANY
preferences.putInt(WIDTH, bounds.width);
preferences.putInt(HEIGHT, bounds.height);
try {
preferences.flush(); // 4
}
catch (BackingStoreException e) {
// not crucial; log message
}
}
// for testing
void clearPreferences() throws BackingStoreException {
preferences.clear();
preferences.flush();
}
}
You create a Preference object by passing a class name to one of two Pref-
erences factory methods, userNodeForPackage or systemNodeForPackage, corresponding
to the user and system trees of preference data. You might use the system
preferences tree for application-specific information, such as the location of a
properties file. You might use the user preferences tree for user-specific infor-
mation, such as color choices.
Java stores preferences in trees that are analogous to file system directory
tree structures. Depending on the platform (and specific implementation), the
actual storage location of preferences could be an operating system registry,
the file system, a directory server, or a database.
The PersistentFrame class uses the user preferences tree (see line 1). The
frame initialization code attempts to read the preferences for the frame’s
bounds13 (starting at line 2). Each call to getInt can contain a default value to
use if no corresponding preference data exists.
When the frame gets closed (by client code that invokes the dispose method
or by other means), code in saveWindowPosition stores the current bounds of the
frame using the Preferences API. You must flush the preferences object (line 4)
to force a write to the persistent store. In addition to putInt (line 3) the Prefer-
Java Periphery
ences class supplies other methods to support storing various types of data:
String, boolean, byte[], double, float, and long.
The Preferences API provides methods for writing data to and reading
data from a file in XML format.
13
A Rectangle object specifies the bounds of a window. The (x, y) coordinate repre-
sent its origin, or upper-left corner. The height and width represent the extent of the
window in pixels.
JAVA PERIPHERY 689
System Environment
On rare occasions, you’ll need to know information about your operating en-
vironment that the predefined system properties does not provide. Unix and
Windows operating systems have environment variables, yet again a key-
value pair scheme. From a command-line prompt, you view the environment
variables using the command set.14 From within Java, the System class pro-
vides two getEnv methods to help you retrieve these environment variables and
their values.
14
This works under Windows and under many Unix shells.
15
The Runtime class is designed so that there can only be a single instance of Runtime
in an executing Java application, therefore Runtime is an example of the singleton de-
sign pattern.
690 JAVA MISCELLANY
You will not see a console window when executing the new process. It is
possible, but slightly tricky, to capture output from or siphon input to the
new process. Java redirects the three standard IO streams (stdin, stdout, and
stderr) to Java streams so that you can work with them. Usually you’ll only
need to capture output; that’s what we’ll concentrate on here.
The trickiness to capturing output comes from the fact that the buffer size
of the operating system may be small. You must promptly manage data com-
ing from each output stream. If you do not, your application is likely to hang
as it executes.
package sis.util;
import junit.framework.TestCase;
import java.util.*;
static { // 2
output.put(TEST_SINGLE_LINE, "a short line of text");
output.put(TEST_MULTIPLE_LINES, "line 1\\nline 2\\");
output.put(TEST_LOTS_OF_LINES, lotsOfLines());
}
Avoid use of command-line redirection (> and <) or piping (|)—these will
be considered part of the command input string and will not be appropriately
handled by the operating system.
The ProcessBuilder class allows you to pass new environment variables
and values to the new process. You can also change the current working di-
rectory under which the process executes.
One thing that ProcessBuilder provides that the Runtime.exec technique does
not is the ability to merge the stdout and stderr streams. Console applications
often interweave these: You might get a prompt or other output message that
comes from stdout, then an error message from stderr, then a stdout prompt, and
so on. If stdout and stderr are separated into two streams, there is no way to
determine the original chronological sequencing of the combined output. See
the API documentation for details on how to control this feature.
Avoid use of things such as getEnv and ProcessBuilder that intro-
duce dependencies on your operating environment.
For further information, refer to the article “Create a Custom Java 1.2-
Style Classloader” by Ken McCrary at http://www.javaworld.com/java-
world/jw-03-2000/jw-03-classload.html. For more-up-to-date information,
start with the API documentation for the class java.lang.ClassLoader.
Weak References
Java’s garbage collection scheme manages reclaiming memory for objects that
you’re done with. But what if you want to write an application that monitors
memory use? In order to monitor an object, you must hold a reference to it.
As long as your monitor application retains such a reference, the garbage col-
lector cannot reclaim the object in question.
You might also want to implement a caching scheme. A cache loads fre-
quently accessed objects. But since the cache data structure must (by defini-
tion) refer to cached objects, the garbage collector can never consider them
for reclamation. You must write complex additional code to manage remov-
ing objects from the cache from time to time. Otherwise your cache will con-
tinue to grow until you get an out-of-memory error.
To help with these and other problems, Java lets you use weak references.
A weak reference to an object does not count for purposes of garbage collec-
tion. The garbage collector will reclaim any object with weak references but
no strong references.
Java supplies three levels of weak references, in order from weakest to
strongest:16 phantom, weak, and soft. A phantom reference can be used for
special cleanup processing after object finalization. A weak reference results
in the referred object being removed when garbage is collected. A soft refer-
ence results in the referred object being removed only when the garbage col-
lector determines that memory is really needed.
The class java.util.WeakHashMap is a Map implementation with weak
keys. Refer to its API documentation for further details.
Refer to the Java API documentation for the package java.lang.ref for
more information on the various weak reference types.
What Else Is
There? The finalize Method
In Lesson 4, you learned a bit about garbage collection. Java automatically
collects unused objects—ones that no other objects refer to. Once in a while,
you may find the need to accomplish something when an object is garbage
collected. To do so, you might consider defining a finalize method:
16
[JavaGloss2004b].
WHAT ELSE IS THERE? 695
The garbage collector calls the finalize method against any objects that it
collects.
The problem is that you cannot guarantee that finalize ever gets called.
Even when the Java VM shuts down—when your application exits—finalize
might not get called. This means that you should never write code in finalize
that must execute. Closing file or database connections in a finalize method is
not a good idea—chances are that these resources will never be released.
If you think you need a finalize method, try to find a way to redesign your
code. Or consider the use of phantom references as an alternative. If you do
implement finalize, it is a good idea to ensure that you always call the super-
class finalize method (as shown in the above bit of code).
Instrumentation
Using Java instrumentation capabilities, you can add informational byte
codes to Java class files. This information can be used to aid in logging mes-
sages, profiling method execution times, or building code coverage tools. The
goal is to allow you to insert such information in a manner that does not
alter the functionality of the existing application.17
To use instrumentation, you implement the ClassFileTransformer interface.
You register this implementation at the java command line using the -javaagent
switch. As the class loader attempts to load a class, it calls the transform method
in your ClassFileTransformer implementation. The goal of the transform method
is to return a byte array representing the replacement class file.
Refer to the Java API documentation for the package java.lang.instrument
for more information.
What Else Is
There?
Management
The Management API allows you to monitor and manage the Java VM. It
also allows for some management of the OS under which the VM is execut-
ing. The Management API allows you to do things such as compare perfor-
17
Java’s instrumentation API is an example of a scheme known as aspect-oriented
programming.
696 JAVA MISCELLANY
Networking
The java.net package supplies several classes to help you build network appli-
cations. This package provides support for low-level communications, based
on the universal concept of a bidirectional communication mechanism
known as a socket. Sockets allow you access to the TCP/IP protocol. Sockets
are the underpinning of most host-to-host communication.
The java.net package also provides a number of higher-level APIs centered
on web programming and URLs (Universal Resource Locators).
For more information, refer to the API documentation for the package
java.net.
NIO
Java NIO (“New I/O”) is an advanced, high-performance facility for input-
output operations. If you need to boost your performance for mass data
transfers, you many want to consider NIO. While Sun has retrofitted many
of the java.io streams to use NIO, you may want to directly interact with the
NIO API for the fastest possible data transfers.
NIO is not stream based. You instead use channels, which are similar to
streams in that they are sources and sinks for data. A buffer contains chunks
of data that channels read from and write to. The main speed gain in NIO
comes from the use of direct buffers. Normally, data is copied between Java
arrays and VM buffers. Direct buffers are allocated directly within the VM
and allow your code to directly access them. This avoids the need for expen-
sive copy operations.18
NIO is nonblocking. Normally when you execute an I/O operation, calling
What Else Is
There? code is blocked—it must wait until the operation completes. Using nonblock-
ing I/O, your code can continue to execute even if a read or write operation is
taking place. You can use nonblocking socket channels as the foundation for
socket servers. This increases the scalability of the socket server and simpli-
fies the management of incoming connections and requests. Blocking I/O
18
[Travis2002], pp. 2–3.
WHAT ELSE IS THERE? 697
requires the judicious use of multithreading, while NIO allows you to man-
age all incoming requests in a single thread.
Conceptually, NIO is more complex than the standard java.io library. The
tradeoff is worth it if performance is paramount. In the case of socket
servers, NIO is definitely the way to go. For most other needs, the standard
I/O library will suffice.
For more information, refer to the Sun NIO document at http://java
.sun.com/j2se/1.4.2/docs/guide/nio/index.html.
JNI
The Java Native Interface (JNI) is your hook to the outside world. You might
need to interact with a hardware API written in C, or perhaps you might
need to call operating system routines that go beyond the capabilities of Java.
Using JNI, you can call libraries (DLLs in Windows and sos in Unix) written
in other languages including C++ and C.
For more information, the Sun tutorial is a good place to start (but it may
be somewhat out of date): http://java.sun.com/docs/books/tutorial/
native1.1/stepbystep/index.html.
As with any Java technology that allows you to interact with external re-
sources, minimize your use of JNI in order to maximize your potential for
cross-platform deployment.
RMI
Remote Method Invocation (RMI) is an API that allows you to call methods
on Java objects that execute in the context of another Java VM. The other
VM can be on the same machine or on a remote machine. RMI is the basis
for the distributed component-based computing used in EJBs (Enterprise Java
Beans).
RMI uses the proxy design pattern. A client object can interact with a
server method located on another machine by working through its public in-
terface. In reality, the client interacts with a client-side stub. The stub takes a What Else Is
Java message and translates it into serialized objects that can be sent across There?
the wire. The stub interacts with a server-side skeleton, the job of which is to
take those object streams and translate them into a method call against the
actual server class.
You generate the stub and skeleton Java source using the RMI compiler
rmic, located in your Java bin directory.
Using RMI keeps your clients from having to build remote communication
code. As far as your client code knows, it is interacting with just another Java
698 JAVA MISCELLANY
object in the same virtual machine. Network limitations will inherently slow
your application, however, so you must still design your application with the
high overhead of RMI in mind. The primary design goal for distributed ap-
plications is to minimize the amount of distributed processing—don’t distrib-
ute your objects unless you must!19
Beans
You use JavaBeans, not to be confused with Enterprise Java Beans (EJBs), for
building pluggable GUI components. You might build a new Java stoplight
GUI control that you want to sell to Java GUI developers. The stoplight Java-
Bean is a Java class like any other. But in order for developers to load and use
your stoplight control in tools such as JBuilder, it must adhere to JavaBeans
specifications.
To be a visual JavaBean, a class must inherit from java.awt.Component
and must be serializable. It must expose its information using accessor meth-
ods that follow the standard Java naming convention (e.g. getName to return
the value of the field name). The ability of tools to gather bean information
based on knowledge of these conventions is known as introspection. A bean
has a related BeanInfo class that provides relevant bean metadata. Beans
communicate with other beans using an event mechanism.
For more information, the best place to start is the Sun tutorial at
http://java.sun.com/docs/books/tutorial/javabeans/.
Security
Security in Java is easily a topic for a whole book. The J2SE security API, lo-
cated in various packages starting with the name java.security, includes classes
for things that include certificate management, key store management, policy
files, encryption/decryption algorithms, and access control lists.
The Java security model is based on the concept of a sandbox, a customiz-
able virtual place. In the sandbox, Java programs can execute without ad-
verse impact to the underlying system or its users.
What Else Is
The core J2SE security API grows with each new release of Java. For full in-
There? formation on what’s available, refer to the Java API and release documentation.
J2EE
The Java 2 Platform Extended Edition (J2EE) is a collection of various enterprise-
level APIs. J2EE can allow you to create scalable component-based applications.
19
[Fowler 2003a], p. 89.
WHAT ELSE IS THERE? 699
Often when people refer to J2EE, they specifically mean Enterprise Java
Beans (EJBs). EJBs are components. The goal of EJBs is to eliminate the need
for an application developer to re-create deployment, security, transaction,
and persistence services. An EJB application server such as BEA’s WebSphere,
IBM’s WebLogic, or JBoss is a container that provides these services for EJBs.
J2EE is heavily weighted toward XML as the common data language.
Four APIs center on XML: the Java API for XML Processing (JAXP), the
Java API for XML-based RPC (remote procedure calls) (JAX-RPC), SOAP
with Attachments API for Java (SAAJ), and the Java API for XML Registries
(JAXR).
Another set of J2EE APIs centers on Web development. The Java Servlet
API is the basis for web applications. JavaServer Pages (JSPs) simplify the
presentation layer for web applications. The JSP Standard Tag Library (JSTL)
provides common custom tags to simplify writing JSPs. JavaServer Faces
gives you a framework for simplifying the development of the model and ap-
plication facets of a web application.
Various other J2EE APIs support transactions, resource connections, and
security. The Java Message Service (JMS) provides an interface for asynchro-
nous message needs.
Scads of books exist on J2EE. The J2EE tutorial available at http://
java.sun.com/j2ee/download.html is as good a place as any to start. Before
you leap into the extremely complex world of J2EE, ensure that you have
justified your decision from the standpoints of functionality need, cost of de-
velopment, complexity requirements, performance need, and scalability
needs. Many shops invested heavily in J2EE, only to find that they didn’t
need most of the things it provides. Often you will find that your own cus-
tom implementation provides all you need, is vastly simpler, and is a far more
flexible solution.
More?
Dozens more Java APIs exist. They support various things such as image ma-
nipulation, speech, applets, cryptography, shared data, telephony, accessibil- What Else Is
ity, and automatic web installation. There?
Java is an entrenched technology that will not go away anytime soon. The
evidence is the fact that for virtually every computing need, a Java API for it
exists. The best place to start is Sun’s Java site, http://java.sun.com. Beyond
that, the world is a haystack with many needles. Use Google to find them!
Go fish!
This page intentionally left blank
Appendix A
This appendix lists key terms. Most terms that appear in the text of Agile
Java in italicized form appear here.
Each definition is brief but should provide you with a good idea what a
given term means. Refer to the index in order to locate occurrences of the
term within Agile Java. The context should provide you with a more robust
understanding of a specific term.
701
702 AN AGILE JAVA GLOSSARY
An Agile Java cast to tell the compiler that you want to convert the type of a reference; to
Glossary narrow a numeric value to a smaller type
catch to trap a thrown exception
checked exception an exception that your code must explicitly acknowl-
edge
AN AGILE JAVA GLOSSARY 703
dependency a relationship between two classes (or any two entities) where
one requires the existence of the other
dependency inversion designing class associations so that classes are depen-
dent upon abstractions instead of concrete implementations
deprecated Java code intended for eventual removal from a class library
An Agile Java
Glossary dereference to navigate to a memory location based on the memory address
stored in a reference
derived class see “subclass”
deserialize to “reconstitute” an object from a sequence of bytes
AN AGILE JAVA GLOSSARY 705
object an entity that has identity, type, and state; objects are created from
classes
object-oriented based on the concepts of classes and objects
operator a special token or keyword recognized by the compiler. An opera-
tor takes action against one or more values or expressions
overload to supply multiple definitions for a method or constructor. You
vary the definitions by their argument list (or possibly their return value; see
covariance)
overloaded operator an operator that has different meanings depending on
the context in which it is used
override to provide a replacement definition for a method already defined
in a superclass
package an arbitrary collection of classes, defined either for deployment or
organizational purposes
package import a form of the import statement that indicates that any of
the classes in the specified package might be used in the class
package structure the current state of package organization
pair programming a development technique in which two developers ac-
tively program at a single workstation
parameter informally, another name for an argument
parameterized type a class to which you can bind one or more classes;
doing so supplies additional constraints on the types of object that the class
can interact with
persistent existing from application execution to execution
platform an underlying system on which applications can be run
polymorphism the ability of objects to supply variant behaviors, in re-
sponse to a message, based on their type. Seen from the opposite direction,
the ability of clients to send a message to an object without an awareness or
concern for which object actually receives the message
postcondition an assertion that must hold true after executing code; see
also design by contract An Agile Java
postfix operator a unary operator that appears after the target it oper- Glossary
ates on
precondition an assertion that must hold true prior to executing code; see
also design by contract
710 AN AGILE JAVA GLOSSARY
recursive calling oneself; sending a message to the same method that is cur-
rently executing
refactor to transform code; by definition, refactoring should not change the
behavior of code
refactoring (n.) a code transformation; (v.) the act of applying transforms to
code
reference a memory address; a variable that points to an object
reflective of or dealing with reflection
reflection the capability in Java that allows you to dynamically derive infor-
mation about types and their definitions at runtime
regression test a test that ensures that the existing application still works
after changes have been applied
regular expression a set of syntactical elements and symbols that is used to
match text
resource bundle a collection of resources such as message strings; allows an
application to dynamically load said resources by name. A resource bundle is
usually an external file.
rethrow to catch and subsequently throw the same or another exception
object
return type indicates the class of object that a method must provide to the
caller
runtime refers to the scope of execution of a program; i.e., when it is
running
sandbox a customizable virtual Java place that defines boundaries for secu-
rity purposes
scale the number of digits to the right of the decimal point in a decimal
number
scope the lifetime of an object or variable; the sphere of influence it has
seed a number used as the basis for determining a pseudorandom number
sequence
semantic equality when two objects are considered the same based on arbi-
trary criteria as supplied by a programmer. In Java, you use the equals method An Agile Java
to define semantic equality Glossary
upper camel case a Java identifier naming scheme; camel case in which the
first letter of the identifier is upper case
unary operator an operator that has a single target
714 AN AGILE JAVA GLOSSARY
An Agile Java
Glossary
Appendix B Java Operator
Precedence
Rules
Java Operator
Precedence Rules
The table in this appendix lists the complete set of operators.1 Operators
grouped on the same line have the same precedence. The table orders prece-
dence groups from highest to lowest. In contrast with the binary operators
+ and -, the unary operators + and - are used in conjunction with a numeric
literal or variable to express whether it is positive or negative.
postfix [] . () ++ —
unary ++ — + -
multiplicative * / %
additive + -
equality == !=
and &
exclusive or ^
inclusive or |
conditional or ||
ternary (conditional) ?:
1
[Arnold 2000].
715
This page intentionally left blank
Appendix C
IDEA
This appendix demonstrates how to build and execute the “Hello World” ap-
plication using IntelliJ IDEA. Before you attempt to use a sophisticated IDE
such as IDEA, you should first learn how to do command-line compilation,
execution, and testing of Java code. Learning these fundamentals will ensure
that you can use Java on any platform you encounter.
IDEA
IDEA is a Java IDE built by a Czech company called JetBrains. IDEA has
been one of the driving forces behind Java-specific intelligent features in an
IDE. Many of the valuable features sported by other Java IDEs appeared first
in IDEA. IDEA is also an open tool, meaning that third-party vendors and in-
dividual developers can enhance its capabilities by writing plug-ins for it.
I’ve used IDEA for several years, alternating between it and Eclipse as my
primary IDEs (the choice my client often makes). One thing I’ve noted about
IDEA is that it promotes the notion of least surprise: Generally, if you want
to do something, IDEA does it and in a way you might expect or hope for.
IDEA also happened to support all of the new features of J2SE 5.0 well
before Eclipse, in time for me to write this appendix before my deadline.
You can obtain the latest version of IDEA from the JetBrains web site at
http://www.jetbrains.com. The version I used at the time of writing this ap-
pendix was IntelliJ IDEA 4.5.1. Follow the instructions at the site for down-
loading and installing IDEA. You should have already installed Java before
bothering to install IDEA.
The installation program walks you through a number of questions re-
garding how you want IDEA configured. In general, you can go along with
the defaults and just hit the Next button.
The following instructions are intended to be a brief introduction to getting
started with IDEA, not a comprehensive user’s guide. If you need more infor-
717
718 GETTING STARTED WITH IDEA
mation, refer to the help supplied with IDEA or contact JetBrains support. Ex-
pect that the instructions shown in this appendix should not change dramati-
cally in the near future, barring any significant changes from JetBrains.
The Hello
Project
Figure C.1
1
IDEA saves information on recently opened projects, so usually you will not need to
worry about the location.
THE HELLO PROJECT 719
The Hello
Project
Figure C.2
The initial project screen, shown in Figure C.3, is spartan. A tab marked
Project appears in the upper left corner, oriented vertically. Click this tab to
open the Project tool window (see Figure C.4).
Select the top entry in the hierarchy tree—the first entry named hello. This
is the project. The next entry below, also hello, is the module. Simple projects
you build usually will need to contain only one module.
Figure C.3
720 GETTING STARTED WITH IDEA
The Hello
Project
Figure C.4
Figure C.5
THE HELLO PROJECT 721
Click your right mouse button after selecting the hello project in order to
bring up the project context menu. The first entry in this menu is New; select
it. You should see a screen that looks like Figure C.5.
Click the menu item Class. When prompted for a class name, enter Hello.
Make sure you enter the class name with the correct case! You should see an The Hello
Project
editor with a starting definition for the class Hello (see Figure C.6).
In the Hello.java editor window, change the code to reflect the Hello code
as originally presented in Chapter II, Setting Up:
class Hello {
public static void main(String[] args) {
System.out.println("hello world");
}
}
The Project tool window to the left now shows the expanded project hier-
archy. Beneath the src entry, you see your new class Hello listed. Right-click
this class entry to bring up the context menu. See Figure C.7.
Click the menu entry Run "Hello.main()", third from the bottom of the
menu. Note that you can press the key combination Ctrl+Shift+F10 as an al-
ternative to using the menu. You need not save your source files before at-
tempting to run programs—IDEA saves them for you automatically.
Figure C.6
722 GETTING STARTED WITH IDEA
The Hello
Project
Figure C.7
IDEA might present you with a dialog asking whether or not you want to
create an output directory. If so, simply click Yes.
A Compile Progress dialog should appear briefly. Before IDEA can execute
your program, it must compile it to ensure there are no problems. The first
time you compile, it may take a little longer; subsequent runs should begin
more quickly.2 Presuming that you have no compile errors, you should see an
output window at the bottom of IDEA (see Figure C.8).
Success!
Next, you’ll change the source code to deliberately contain errors in order to
see how IDEA deals with them.
Change the source code in the Hello.java editor to something that should
generate an error. Here’s one possibility:
class Hello {
public static void main(String[] args) {
xxxSystem.out.println(“hello world”);
}
}
2
The slow initial compilation appears to occur each time you start IDEA.
THE HELLO PROJECT 723
The Hello
Project
Figure C.8
Observe that off to the right, there are a couple of visual indicators telling
you that the code has problems. In the upper right corner of the editor a
small square will appear; it will be red. It was green until you mucked up the
code. Below this square, you will see one or or more thin red lines. These
lines indicate the problem spots in the code. You can hold your mouse over a
line to see the error message. You can click on the line to navigate to the
problem spot.
Try running Hello again and see what happens. Since you’ve already exe-
cuted Hello one time, you can quickly execute it again using one of at least
two ways: either press the Shift-F10 key combination or click the Run arrow
(a green arrow pointing right, like the play button on a CD player). The Run
arrow is located on the toolbar beneath the menu and above the Hello.java
editor.
Instead of seeing the Run tool window with the correct output, you see the
Messages tool window (Figure C.9). This window presents you with a com-
plete list of error messages. Off to the left are various icons that help you
manage the messages. Hold your mouse over any one of the icons for a sec-
ond or so to see a tool tip that explains the icon’s use.
If you double-click a specific error message in the Messages tool window,
IDEA will put the text cursor on the specific location in Hello.java that con-
tains the error. Correct the error and re-run Hello.
724 GETTING STARTED WITH IDEA
Running Tests
Figure C.9
Running Tests
This section walks you through the first part of Lesson 1, in which you cre-
ate a StudentTest class and a corresponding Student class. The goal of this
section is to show you how to effectively do test-driven development in
IDEA.
Start by creating a new project. Click on the File menu, then click New
Project. IDEA will present you with the same series of dialogs as when you
created the hello project. This time, type the name of the project as lesson1.
Then follow the same steps as when you created the hello project—keep
clicking Next. When you click Finish, IDEA gives you the opportunity to
open the project in a new frame. Select Yes.
Before you enter code, you will need to change some project settings.
From the Project tools window, select the Project (lesson1). Right-click to
bring up the context menu and select Module Settings.
You should see the Paths dialog, shown in Figure C.10.
First, you must configure the lesson1 module to recognize the J2SE 5.0
language level (if it doesn’t already). Near the bottom of the dialog is a drop-
down list marked Language level for project (effective on restart). The paren-
RUNNING TESTS 725
Running Tests
Figure C.10
thesized comment suggests that you’ll need to exit IDEA and restart it for
changes to be effective.3 You’ll do this shortly. Now, click on the drop-down
list to expose the selection list. Choose the entry that indicates version 5.0.
Next, you must specify where the module can find the JUnit JAR file. (If
you’re not sure what this means, refer to Lesson 1 as well as Chapter II on
setting up.) Click on the tab marked Libraries (Classpath) (it appears at the
top of the Paths dialog). Your Paths dialog should look like the screen snap-
shot shown in Figure C.11.
Currently the Paths dialog shows that you have no libraries (JAR files) on
your classpath. Click the button Add Jar/Directory. You will see a Select Path
dialog. Navigate to the directory in which you installed JUnit, select junit.jar,
and click OK. You should be back at the Paths dialog.
Click OK to close the Paths dialog. Exit IDEA, then restart IDEA. Make
sure you completely exit IDEA—close the hello project frame if it remains
open.
3
While some of the new features of J2SE 5.0 will work, others will not do so until
you restart.
726 GETTING STARTED WITH IDEA
Running Tests
Figure C.11
IDEA will probably reopen with the hello project, not the lesson1 project.
If it does so, select File→Close Project from the menu. Then click File→Re-
open and select the lesson1 project from the list of recently opened projects.
If the lesson1 project does not appear, click File→Open Project. You will then
need to navigate to the directory in which you created the project. If you ac-
cepted the defaults, under Windows this directory is probably C:\Documents and
Settings\userName\IdeaProjects\lesson1, where userName is your Windows login name.
Once you have reopened lesson1, right-click in the Project tool window.
Select New→Class from the context menu. For the class name, enter Stu-
dentTest. In the StudentTest.java editor, enter the initial code from Lesson 1
for the StudentTest class:
public class StudentTest extends junit.framework.TestCase {
}
From the Project tool window tree, right-click and select Run
“StudentTest”. Create an output directory if asked to do so. You should see
something like the window in Figure C.12.
RUNNING TESTS 727
Running Tests
Figure C.12
The pane in the lower right quadrant shows output from the test run. This
is JUnit output—IDEA has integrated JUnit directly into the IDE.
If you can’t see enough of the output, drag the slider bar up. The slider bar
appears just above the Run tool window and separates the top half of IDEA
from the bottom half. Move the mouse slowly over the slider until you see a
double-headed north-south arrow, then click and drag.
Make sure you’re following along with the directions in Lesson 1 as you
proceed. As expected, the Output window shows a failure, since you have de-
fined no tests in StudentTest. Modify the StudentTest code:
Rerun (Shift-F10). Now, instead of a red bar and an error in the Output
window, you see a green bar and two lines of output. The first line shows the
actual java command passed to the operating system. The second line says
that the “Process finished with exit code 0.” Good. You demonstrated fail-
ure, corrected the problem, then demonstrated success.
Next, the fun part. Modify the StudentTest code again:
728 GETTING STARTED WITH IDEA
Figure C.13
TAKING ADVANTAGE OF IDEA 729
Taking
Advantage of
IDEA
Figure C.14
fields. This gives you the opportunity to replace template code with your
specifics. Tab to the argument field and replace the argument name s with
name. Tab again or press the Esc key to exit template mode.
You should see no red indicators. Rerun and ensure you see green results
from JUnit.
Continue with the remainder of the Lesson 1 exercise. Take advantage of
the various intention actions as IDEA presents them to you. Experiment with
alternate ways of effecting things. If you’ve been leaning toward mouse-
directed options, try the key combinations to see if they help you go faster.
And vice versa! Dig through the menus available and see if you can effect ac-
tions from there.
Code Completion
One valuable feature is code completion, which I’ll also refer to as auto-
completion. When you begin typing things that IDEA can recognize, such as
Taking class names, package names, and keywords, click Ctrl-Space. IDEA will do
Advantage of one of three things: It may say “No suggestions,” it may automatically com-
IDEA plete the text you began typing, or it may bring up a list of selections to
choose from. You can navigate to the selection using the cursor (arrow) keys
and press Enter or you can click on the desired selection. IDEA will finish
your typing based on this selection.
Pressing the Enter key results in the new text being inserted at the current
location. If you instead press the Tab key, the new text will replace any text
immediately to the right.
Get in the habit of pressing Ctrl-Space after typing two or three significant
characters. I press it all the time. You may be surprised at what IDEA is able
to figure out about your intentions.
IDEA provides two additional kinds of code completion: Smart Type and
Class Name. These are more sophisticated tools triggered by alternate key-
strokes. Refer to the IDEA help system for more information (Help→Help
Topics).
Navigation
One of the most significant strengths of using an IDE such as IDEA is the
ease with which you can navigate through Java code. Click on the Go To
menu. The size of this menu suggests how flexible this feature is. Click on the
Class selection from the Go To menu. Type the three letters Stu. Pause. You
will see a drop-down list of appropriate classes to choose from.
Class navigation is basic but important. More valuable is the ability to
navigate from directly within code. Open the StudentTest class. From the line
that reads new Student(“Jane Doe”);, click on the class name Student. Then press
the Ctrl-b key combination (b for “browse,” I suppose). Pressing Ctrl-b is
equivalent to selecting the menu option Go To→Declaration.
IDEA will navigate directly into the Student constructor. You can return to
your previous edit location by using the key combination Ctrl-Shift-
Backspace. Learning all these navigation shortcuts will save you considerable
time. I’ve watched programmers, even recently, use inferior tools. They waste
several minutes every hour in manually navigating through a list of classes
and scrolling through large amounts of code.
Searching is another form of navigation. IDEA understands Java and how
it all fits together, making searches far more effective. Suppose you want to
TAKING ADVANTAGE OF IDEA 731
find all code that invokes a certain method. Using a programmer’s editor, you
can do only text searches against code. Suppose you’re trying to find all
client classes that call a method named getId. There might be other classes
that implement getId; the text searches will report uses against these classes as
well. Taking
Advantage of
But IDEA can use its Java smarts to help you find only the usages you’re IDEA
interested in. As an example: In the Student class, click on the constructor.
Right-click and select Find Usages from the context menu. Click OK when
presented with the Find Usages dialog. The Find tool window will appear at
the bottom of IDEA. As with the Messages tool window, you can double-
click an entry in the find results to navigate directly to it.
Refactoring
Another important capability is the built-in refactoring support. IDEA auto-
mates a number of refactorings that allow you to quickly and safely trans-
form your code. Take a look at all the entries in the Refactor menu to get an
idea of the things you can do.
The simplest and perhaps most valuable refactoring appears first—Re-
name, also effected by Shift-F6. In a few keystrokes, you can safely change
the name of a method. This means that IDEA also changes any other code
that calls the method to reflect the new name. It also shows you the proposed
rename before it applies it to your code. This allows you to back out if you
recognize a problem with the potential refactoring.
I use the Rename refactoring all the time. Rather than waste a lot of time
coming up with the “perfect” method or class name, I’ll often go with a
name I know to be inferior. This allows me to immediately get moving. After
coding a little, a better name usually suggests itself. A quick Shift-F6 and I’m
on my way. Sometimes I might change a method name more than a couple of
times before I’m satisfied.
Code Analysis
IDEA supplies you with a code analysis feature known as the code inspector
(Analyze→Inspect Code). You execute code inspection on demand against a
single source file or the entire project. The inspector looks for problem spots
in your code, including things such as unused methods or variables or empty
catch blocks. The list of options includes over 300 categorized entries. You
pick and choose what you’re interested in: individual entries or groups of re-
lated entries or both.
732 GETTING STARTED WITH IDEA
The inspection can take a bit of time to execute. When an inspection com-
pletes, the inspector presents you with a tree showing all the results. Each re-
sult is a detailed explanation of the problem or potential problem in your
code. From a detailed explanation, you can click to navigate to the corre-
Taking sponding trouble spot. When possible, IDEA will give you the option to au-
Advantage of
IDEA
tomatically correct the problem.
IDEA may report dozens or hundreds of entries that you may not think
are problems. It’s up to you and your team to decide the items that are rele-
vant. Some items are not problems at all. But virtually everything on the list
can point to the potential for trouble in certain environments or circum-
stances.
The practice of TDD can mitigate most of these concerns. In Agile Java,
you’ve learned to build things incrementally and not always to an overly ro-
bust expectation. You can “get away” with things that would be deemed un-
acceptable in the absence of good testing.
Relax. Some others might consider the style in Agile Java to be fast and
loose. Certainly there is always room for improvement in my code. But I
don’t worry about it as much when doing TDD.
I consider the bulk of code I come across difficult to understand and main-
tain. Blindly adhering to standards can seem like a good idea, but often it can
lead you to waste considerable time for little gain. If you instead look to the
simple design goals of testability, elimination of duplication, and code ex-
pressiveness, you will rarely go wrong.
Agile Java References
733
734 AGILE JAVA REFERENCES
[Langr2001] Langr, Jeff. “Evolution of Test and Code Via Test-First Design.”
http://www.objectmentor.com/resources/articles/tfd.pdf.
[Langr2003] Langr, Jeff. “Don’t Mock Me.” http://www.LangrSoft.com/
articles/mocking.html.
[Lavender1996] Lavender, R. Greg; Schmidt, Douglas C. “An Object Behav-
ioral Pattern for Concurrent Programming.” http://citeseer.ist.psu.edu/
lavender96active.html.
[Link2003] Link, Johannes. Unit Testing in Java: How Tests Drive the Code.
Morgan Kaufmann, 2003.
[Martin2003] Martin, Robert. Agile Software Development: Principles, Pat-
terns, and Practices. Prentice Hall, 2003.
[Massol2004] Massol, Vincent. JUnit in Action. Manning Publications,
Agile Java
Refernces
2004.
[McBreen2000] McBreen, Pete. Software Craftmanship. Addison-Wesley,
2001.
[Rainsberger2005] Rainsberger, J. B. JUnit Recipes. Manning Publications,
2005.
[Sun2004] [Java] Reference Glossary. http://java.sun.com/docs/glossary.html.
[Travis2002] Travis, Gregory M. JDK 1.4 Tutorial. Manning Publications,
2002.
[Venners2003] Venners, Bill; Eckel, Bruce. “The Trouble with Checked
Exceptions: A Conversation With Anders Hejlsberg, Part II.” http://www
.artima.com/intv/handcuffs.html.
[Vermeulen2000] Vermeulen, Allan, et al. The Elements of Java Style. Cam-
bridge University Press, 2000.
[WhatIs2004] “platform.” http://www.whatis.com.
[Wiki2004] “CodeSmell.” http://c2.com/cgi/wiki?CodeSmell.
[Wiki2004a] “EmptyCatchClause.” http://c2.com/cgi/wiki?EmptyCatchClause.
[Wiki2004b] “SimpleDesign” and “XpSimplicityRules,” http://c2.com/
cgi/wiki?SimpleDesign and http://c2.com/cgi/wiki?XpSimplicityRules.
[Wikipedia2004] http://en.wikipedia.org/wiki/Java_programming_language.
Index
A @param, 99
@Retention annotation, 545
^ (exclusive-or operator), 312–313 @return, 99
^ (xor (exclusive-or) operator, 362 @Target annotation, 546–547
| (bit-or operator), 362, 364 abbreviations, 60
| (non-short-circuited or operator), abstract, 204–205
313 abstract classes, 204–205
|| (logical or operator), 312 abstract test pattern, 221–223
~ (logical negation operator), 362, abstraction, 13, 188
365 AbstractTableModel
! (not operator), 312 (javax.swing.table), 637
% (modulus operator), 355 access modifiers, 122–124,
& (bit-and operator), 362, 364 212–213
&& (and operator), 312 action methods, 133
+ (add operator), 67 ActionListener (java.awt.event), 587
+ (String concatenation), 106, 344 actionPerformed (java.awt.event.Ac-
++ (increment operator), 144–145 tionListener method), 587
— (decrement operator), 144 activation, 476
; (statement terminator), 39 active object pattern, 462
<< (bit shift left operator), 367 actual value, 48
= (assignment operator), 42–43 adapter, 435–438 , 590–591
== (reference comparison operator), additional bounds, parameterized
346 types, 525–526
>> (bit shift right operator), 367 advanced streams, 399
>>> (unsigned bit shift right opera- agile, 9–11
tor), 367 algorithms, hash code, 333–336
? (wildcard character), 519 American Standard Code for Infor-
? : (ternary operator), 245–246 mation Interchange
\n (line feed escape sequence), 105, (ASCII), 105
109 and operator (&&), 312
^ (bit-xor operator), 362, 365–367 annotations, 537–538
^ (logical exclusive or operator), @Retention, 545
312 @Target, 546–547
@deprecated, 537–538 @TestMethod, 543–545, 548
@Override annotation 216, 217, array parameters, 553–554
325, 345, 538 compatibility, 561
735
736 INDEX
informit.com/onlinebooks
relevance-ranked results in a matter of seconds.
■ Immediate results.
With InformIT Online Books, you can select the book
you want and view the chapter or section you need
immediately.
Online Books
errors. Make notes on the material you find useful and
choose whether or not to share them with your work
group.
Articles
Online Books
Catalog