Table of Contents
1 Introduction 1
2 Creating and Destroying Objects 5
3 Methods Common to All Objects 33
4 Classes and Interfaces 67
5 Generics 109
6 Enums and Annotations 147
7 Methods 181
8 General Programming 209
9 Exceptions 241
10 Concurrency 259
11 Serialization 289
Appendix Items Corresponding to First Edition 317
References 321
Index 327
Forewords & Introductions
In 1996 I pulled up stakes and headed west to work for JavaSoft, as it was then known, because it was clear that was where the action was. In the intervening five years I've served as Java Platform Libraries Architect. I've designed, implemented and maintained many of the libraries, and served as a consultant for many others. Presiding over these libraries as the Java platform matured was a once-in-a-lifetime opportunity. It is no exaggeration to say that I had the privilege to work with some of the great software engineers of our generation. In the process, I learned a lot about the Java programming language--what works, what doesn't, and how to use the language and its libraries to best effect.
This book is my attempt to share my experience with you, so that you can imitate my successes while avoiding my failures. I borrowed the format from Scott Meyers's Effective C++ Meyers98, which consists of fifty items, each conveying one specific rule for improving your programs and designs. I found the format to be singularly effective and I hope you do too.
In many cases, I took the liberty of illustrating the items with real-world examples from the Java platform libraries. When describing something that could have been done better, I tried to pick on code that I wrote myself, but occasionally I pick on something written by a colleague. I sincerely apologize if, despite my best efforts, I've offended anyone. Negative examples are cited not to cast blame but in the spirit of cooperation, so that all of us can benefit from the experience of those who've gone before.
While this book is not targeted solely at developers of reusable components, itis inevitably colored by my experience writing such components over the past two decades. I naturally think in terms of exported APIs (Application Programming Interfaces) and I encourage you to do likewise. Even if you aren't developing reusable components, thinking in these terms tends to improve the quality of the software you write. Furthermore, it's not uncommon to write a reusable component without knowing it: you write something useful, share it with your buddy across the hall, and before long you have half a dozen users. At this point, you no longer have the flexibility to change the API at will, and are thankful for all the effort that you put into designing the API when you first wrote the software.
My focus on API design may seem a bit unnatural to devotees of the new lightweight software development methodologies, such as Extreme Programming Explained Beck99. These methodologies emphasize writing the simplest program that could possibly work. If you're using one of these methodologies you'll find that a focus on API design serves you well in the refactoring process. The fundamental goals of refactoring are the improvement of system structure and the avoidance of code duplication. These goals are impossible to achieve in the absence of well-designed APIs for the components of the system.
No language is perfect, but some of them are excellent. I have found the Java programming language and its libraries to be immensely conducive to quality and productivity, and a joy to work with. I hope this book captures my enthusiasm and helps make your use of the language more effective and enjoyable.
Josh Bloch
Cupertino, California
April, 2001
Read an Excerpt
Chapter 6: Methods
This chapter discusses several aspects of method design: how to treat parameters
and return values, how to design method signatures, and how to document methods.
Much of the material in this chapter applies to constructors as well as to methods.
Like Chapter 5, this chapter focuses on usability, robustness, and flexibility.
Item 23: Check parameters for validity
Most methods and constructors have some restrictions on what values may be
passed into their parameters. For example, it is not uncommon that index values
must be nonnegative and object references must be non-null. You should clearly
document all such restrictions and enforce them with checks at the beginning of the
method body. This is a special case of the general principle, and you should attempt
to detect errors as soon as possible after they occur. Failing to do so makes it less
likely that an error will be detected and makes it harder to determine the source of an
error once it has been detected.
If an invalid parameter value is passed to a method and the method checks its
parameters before execution, it will fail quickly and cleanly with an appropriate
exception. If the method fails to check its parameters, several things could happen.
The method could fail with a confusing exception in the midst of processing.
Worse, the method could return normally but silently compute the wrong result.
Worst of all, the method could return normally but leave some object in a compromised
state, causing an error at some unrelated point in the code at some undetermined
time in the future.
For public methods, use the Javadoc @throws tag to document the exception
that will be thrown if a restriction on parameter values is violated (Item 44). Typically
the exception will be IllegalArgumentException, IndexOutOfBoundsException, or NullPointerException (Item 42). Once you've documented the restrictions on a method's parameters and you've documented the exceptions that
will be thrown if these restrictions are violated, it is a simple matter to enforce the
restrictions. Here's a typical example:
/**
*Returns a BigInteger whose value is (this mod m).This method
*differs from the remainder method in that it always returns
*nonnegative BigInteger.
*
*@param m the modulus,which must be positive.
*@return this mod m.
*@throws ArithmeticException if m <= 0.
*/
public BigInteger mod(BigInteger m){
if (m.signum()<= 0)
throw new ArithmeticException("Modulus not positive");
... // Do the computation
}
For an unexported method, you as the package author control the circumstances
under which the method is called, so you can and should ensure that only
valid parameter values are ever passed in. Therefore nonpublic methods should
generally check their parameters using assertions rather than normal checks. If
you are using a release of the platform that supports assertions (1.4 or later), you
should use the assert construct; otherwise you should use a makeshift assertion
mechanism.
It is particularly important to check the validity of parameters that are not used
by a method but are stored away for later use. For example, consider the static factory
method on page 86, which takes an int array and returns a List view of the
array. If a client of this method were to pass in null , the method would throw a
NullPointerException because the method contains an explicit check. If the
check had been omitted, the method would return a reference to a newly created
List instance that would throw a NullPointerException as soon as a client
attempted to use it. By that time, unfortunately, the origin of the List instance
might be very difficult to determine, which could greatly complicate the task of
debugging.
Constructors represent a special case of the principle that you should check
the validity of parameters that are to be stored away for later use. It is very important
to check the validity of parameters to constructors to prevent the construction
of an object that violates class invariants.
There are exceptions to the rule that you should check a method's parameters
before performing its computation. An important exception is the case in which
the validity check would be expensive or impractical and the validity check is performed
implicitly in the process of doing the computation. For example, consider
a method that sorts a list of objects, such as Collections.sort(List). All of the
objects in the list must be mutually comparable. In the process of sorting the list,
every object in the list will be compared to some other object in the list. If the
objects aren't mutually comparable, one of these comparisons will throw a ClassCastException, which is exactly what the sort method should do. Therefore there
would be little point in checking ahead of time that the elements in the list were
mutually comparable. Note, however, that indiscriminate application of this technique
can result in a loss of failure atomicity (Item 46).
Occasionally, a computation implicitly performs the required validity check
on some parameter but throws the wrong exception if the check fails. That is to
say, the exception that the computation would naturally throw as the result of an
invalid parameter value does not match the exception that you have documented
the method to throw. Under these circumstances, you should use the exception
translation idiom described in Item 43 to translate the natural exception into the
correct one.
Do not infer from this item that arbitrary restrictions on parameters are a good
thing. On the contrary, you should design methods to be as general as it is practical
to make them. The fewer restrictions that you place on parameters, the better,
assuming the method can do something reasonable with all of the parameter values
that it accepts. Often, however, some restrictions are intrinsic to the abstraction
being implemented.
To summarize, each time you write a method or constructor, you should think
about what restrictions exist on its parameters. You should document these restrictions
and enforce them with explicit checks at the beginning of the method body.
It is important to get into the habit of doing this; the modest work that it entails
will be paid back with interest the first time a validity check fails.
Item 24: Make defensive copies when needed
One thing that makes the Java programming language such a pleasure to use is that it
is a
safe language. This means that in the absence of native methods it is immune to
buffer overruns, array overruns, wild pointers, and other memory corruption errors
that plague unsafe languages such as C and C++. In a safe language it is possible to
write classes and to know with certainty that their invariants will remain true, no
matter what happens in any other part of the system. This is not possible in languages
that treat all of memory as one giant array.
Even in a safe language, you aren't insulated from other classes without some
effort on your part. You must program defensively with the assumption that
clients of your class will do their best to destroy its invariants. This may actually
be true if someone tries to break the security of your system, but more likely
your class will have to cope with unexpected behavior resulting from honest mistakes
on the part of the programmer using your API. Either way, it is worth taking
the time to write classes that are robust in the face of ill-behaved clients.
While it is impossible for another class to modify an object's internal state
without some assistance from the object, it is surprisingly easy to provide such
assistance without meaning to do so. For example, consider the following class,
which purports to represent an immutable time period...